Goals

we’ll do these by taking a close look at some interesting data: Airbnb listings for New York City in May 2017.

It’s okay if you can’t start using these techniques right away after the workshop. To be comfortable with a tool like R takes some time and practice. But we do hope that your exposure today to these tools and techniques will open your eyes about possibilities and motivate you to keep learning.

RStudio

RStudio is a free program that makes writing R code much more enjoyable and efficient.

It has four main panes. This one is the code editor (also known as script editor and source editor). This is where we’ll spend most of our time. We’ll say more about the other panes when the time comes.

R Notebook

Before starting the analysis, let’s understand how this document works. I’m assuming you’re reading this on RStudio.

This is an R Notebook. An R Notebook contains commentary interspersed with code chunks. The code chunks can be run (executed) independently and interactively. The output will appear below the code chunk. R Notebooks are easy to convert to well-formatted final documents as a pdf file, a webpage, or an MS Word file. More here

Click on the preview button above to get an idea.

What you’re reading is the commentary and what you see below is the container for a code chunk.

# The code goes here

To run a code chunk, click on the little triangle at the right edge of the code chunk. Or, use the keyboard shortcut CTRL + SHIFT + ENTER (Windows) or CMD + SHIFT + ENTER (Mac).

Exercise: Run the chunk below and see what happens.

a <- 5 + 7
print(a)
[1] 12

As you see, the output appears under the code. This way you can immediately see the result of the code you write at all steps of the analysis.

(Do you know what’s going on in the code above? We’ll talk about variables and assignments later.)

Feel free to add your commentary and code anywhere in this document. You can always download the unmodified version from here.

To write your own code chunk, look for the insert button above and then select R. Or, use the keyboard shortcut CTRL + ALT + I (Windows) or CMD + OPTION + I (Mac).

Exercise: Place the cursor below and create a container for writing code. Now write some code and run it. For example, find the result of 3543 / 562.

The data

The dataset contains almost all the listings in NYC in May 2017.The data came from Inside Airbnb.

It’s always a good idea to approach a new dataset with a few basic questions. Some examples:

Here’s some background information that may answer some of these questions.

Let’s take a look at the data. Open the csv file in Excel. Browse around a bit. Any observations or questions?

Discuss: Can you explain what each column is about?

Discuss: Are there any data that you’d like to have but not there?

Discuss: What’s the first thing you’d like to find out from these data?

Discuss: What do you think about the quality of the data? Why?

The context

Data analysis happens within a larger context. Usually there’s an overarching business, policy, or scientific question that one would like to answer. Often that question is not very clear. Whatever the case, you need to approach the data with curiosity about the larger context.

The more you understand the context of the data, the better will be your questions and hypotheses guiding your analysis.

For our dataset, we should have a good understanding of the business model of Airbnb as well as the economy and geography of NYC.

Discuss: Is there anything else we should know about?

Airbnb

Discuss: How much do we need to know about Airbnb and its business? Is my personal experience with Airbnb enough? What if I don’t have any personal experience?

How Airbnb works

Example airbnb listing

New York City

We can start with a map of the city.

img

img

The map shows the relative size and location of the five boroughs of NYC.

Discuss: What else do we know about these boroughs? What about the neighborhoods within the boroughs?

Analysis

Install and load libraries

We’ll first install a few libraries that we’ll need at different stages of the analysis. This may take a while.

install.packages("lubridate")
Installing package into 㤼㸱C:/Users/mehedia/Documents/R/win-library/3.4㤼㸲
(as 㤼㸱lib㤼㸲 is unspecified)
trying URL 'https://mran.microsoft.com/snapshot/2017-05-01/bin/windows/contrib/3.4/lubridate_1.6.0.zip'
Content type 'application/zip' length 667329 bytes (651 KB)
downloaded 651 KB
package ‘lubridate’ successfully unpacked and MD5 sums checked

The downloaded binary packages are in
    C:\Users\mehedia\AppData\Local\Temp\Rtmpk3mp83\downloaded_packages

And then we load the libraries for our current R session. After these libraries are successfully loaded, all the functions in these libraries will be available for our use.

library(readr)
library(dplyr)

Attaching package: 㤼㸱dplyr㤼㸲

The following objects are masked from 㤼㸱package:stats㤼㸲:

    filter, lag

The following objects are masked from 㤼㸱package:base㤼㸲:

    intersect, setdiff, setequal, union
library(ggplot2)
library(stringr)
library(lubridate)

Attaching package: 㤼㸱lubridate㤼㸲

The following object is masked from 㤼㸱package:base㤼㸲:

    date

Load and prepare data

We’ll use the function read_csv, which comes from the library readr, to load data from file and convert the data into a dataframe. A dataframe is a tabular data structure, with columns as variables and rows as observations. In this case, exactly as it is in the csv file.

Discuss: What’a csv file? What other formats are out there for storing data? How can we load data from an unfamiliar format?

df <- read_csv("airbnb_newyork.csv")
Parsed with column specification:
cols(
  .default = col_integer(),
  host_since = col_character(),
  host_response_time = col_character(),
  host_response_rate = col_character(),
  neighbourhood = col_character(),
  borough = col_character(),
  property_type = col_character(),
  room_type = col_character(),
  bathrooms = col_double(),
  amenities = col_character(),
  price = col_number(),
  calendar_updated = col_character(),
  cancellation_policy = col_character(),
  listing_url = col_character(),
  description = col_character()
)
See spec(...) for full column specifications.

First thing to check if the data have been successully loaded and assigned to the variable (here df). We don’t see any error — which is a good sign. We can also see the variable df in the environment pane (usually to the right of this code editor pane – click on “Environment” if hidden).

Next thing to check is whether read_csv was able to correctly infer the data type of each variable.

The function glimpse will give us a better glimpse.

glimpse(df)
Observations: 40,752
Variables: 28
$ host_id                     <int> 58306608, 124280354, 124280354, 124280354, 124280354, 1242...
$ host_since                  <chr> "2/11/2016", "4/4/2017", "4/4/2017", "4/4/2017", "4/4/2017...
$ host_response_time          <chr> "within an hour", "within an hour", "within an hour", "wit...
$ host_response_rate          <chr> "0.78", "0.99", "0.99", "0.99", "0.99", "0.99", "0.95", "N...
$ host_listings_count         <int> 1, 7, 7, 7, 7, 7, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1...
$ neighbourhood               <chr> "Hell's Kitchen", "Hell's Kitchen", "Hell's Kitchen", "Hel...
$ borough                     <chr> "Manhattan", "Manhattan", "Manhattan", "Manhattan", "Manha...
$ zip_code                    <int> 10000, 10001, 10001, 10001, 10001, 10001, 10001, 10001, 10...
$ property_type               <chr> "Apartment", "Apartment", "Apartment", "Apartment", "Apart...
$ room_type                   <chr> "Private room", "Shared room", "Shared room", "Shared room...
$ accommodates                <int> 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 1, 2, 1, 1, 2, 2, 1, 1...
$ bathrooms                   <dbl> 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 3.0, 1.0, 1.0, 1.0, 1.0...
$ bedrooms                    <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1...
$ beds                        <int> 1, 1, 1, 1, 1, 1, 2, 6, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1...
$ amenities                   <chr> "{TV,Internet,\"Wireless Internet\",\"Air conditioning\",P...
$ price                       <dbl> 200, 28, 39, 39, 39, 43, 50, 50, 50, 53, 54, 55, 55, 60, 6...
$ calendar_updated            <chr> "never", "5 days ago", "5 days ago", "3 days ago", "6 days...
$ number_of_reviews           <int> 0, 0, 2, 1, 0, 3, 185, 0, 0, 0, 21, 5, 3, 12, 0, 20, 1, 1,...
$ review_scores_rating        <int> NA, NA, 100, 100, NA, 87, 93, NA, NA, NA, 96, 100, 80, 92,...
$ review_scores_accuracy      <int> NA, NA, 10, 10, NA, 9, 9, NA, NA, NA, 10, 10, 10, 9, NA, 1...
$ review_scores_cleanliness   <int> NA, NA, 10, 10, NA, 8, 9, NA, NA, NA, 10, 8, 8, 9, NA, 9, ...
$ review_scores_checkin       <int> NA, NA, 10, 10, NA, 9, 10, NA, NA, NA, 10, 10, 10, 9, NA, ...
$ review_scores_communication <int> NA, NA, 10, 10, NA, 9, 10, NA, NA, NA, 10, 10, 10, 9, NA, ...
$ review_scores_location      <int> NA, NA, 10, 10, NA, 9, 10, NA, NA, NA, 10, 10, 9, 10, NA, ...
$ review_scores_value         <int> NA, NA, 10, 10, NA, 9, 10, NA, NA, NA, 10, 10, 8, 9, NA, 1...
$ cancellation_policy         <chr> "moderate", "flexible", "flexible", "moderate", "flexible"...
$ listing_url                 <chr> "https://www.airbnb.com/rooms/18292044", "https://www.airb...
$ description                 <chr> "NBA player and China star Huge residence apartment, Hugel...

What you see within the angular brackets <...> next to each column name is the data type of that variable.

But what are data types? And, why are they important? What exactly is a variable in programming? What is a function?

Let’s take a detour.

Detour: variables, data types, and functions in R

Variables (programming)

In programming, a variable stores some value (not to be confused with what we call variable in statistics). Our code can then reference the name of the variable for different purposes.

We use the symbol <- (less than sign followed by hyphen) to assign value to a variable name. For example, in the code below, foo and bar are variable names. We assigned the values 43 and "kitten" to foo and bar respectively. You can read them as foo gets 43 and bar gets "kitten".

foo <- 43
bar <- "kitten"
print(foo * 10)
[1] 430

Important: I’ve been using the word variable to mean two different things. A variable in programming is what I described above. A variable in statistics is an attribute of something, whatever the data is about. For example, if the data is about people, some variables could be age, sex, income, address. The names of the columns of a dataframe are variables in the statistical sense. Usually, the context would clarify which meaning is intended.

Tip: Give short descriptive names to your variables. This will make the code easy to follow.

Exercise: a room is 11 yards long and 7.5 yards wide. Assign these values to width and length variables and then calculate the area of the room. You can, of course, give different names to these variables if you wish.

Data types

To understand how R stores and handles information, we need to know a little about data types.

But, first, vector: one of the key concepts in R. Think of a vector as simply a sequence of data. For example, a column in a data table is a vector. This is how you create a vector: c(element1, element2, element3, ...)

vec <- c(3, 5, 10, 20)
print(vec)
[1]  3  5 10 20

A vector can contain four main types of data:

  • Integer: such as 2, 543, 90.
  • Double: numbers other than integers, such as 4.56, 1/33. Doubles are always approximations.
  • Character: a sequence of letters, numbers, punctuations, etc., such as “rainbow”, “34265”. Character data are always written within quotes, either single or double (we’ll always use double for consistency).
  • Logical: data that can take on one of only two values — TRUE or FALSE.

Discuss: Why is “34265” of character type? What is a real world example of this kind of data?

There are two other important ways to store data: - factor, for categorical variables (here “variable” in statistical sense). A categorical variable is one that can take on a limited fixed number of values. - date-time or just date

Functions

The concept of a function is exactly the same as in Excel. You give some values (called parameters) to a function. The function does something with the values and returns a new value.

Here’s a simple function that we named add10. All it does is adds 10 to any number given to it.

add10 <-  function(num){
  new_value <-  num + 10
  return (new_value)
}

And, then, we can use this function whenever we need to add 10 to a number (Of course, we won’t. There are easier ways to add a number).

# Add 10 to 35
add10(35)
[1] 45

Today, we won’t write any function, Rather, we’ll use functions written by others. Here’s an example: the mean function, which comes preloaded in R.

# First let's create a vector
vec2 <- c(23, 53, 11, 34, 87, 100, 5, 12, 66, 9, 87, 110, 20, 33, 54, 43, 76)
# Let's calculate the mean of the vector
calculated_mean <- mean(vec2)
# Then output it with some text description
message("The mean of the vector vec2 is ", mean(calculated_mean))
The mean of the vector vec2 is 48.4117647058824

*Exercise: calculate the sum, median, and standard deviation (hint: sd) of the vector vec2.

Now that we understand data types, let’s load some new data.

Exercise: You’ll see there’s another data file in the folder: “us_babynames.csv”. Load the data from this file to a dataframe (hint: read_csv). Assign the dataframe to a variable named df_babynames. We’ll come back to this dataframe later.

Exercise: Now take a look at the dataframe’s data types. (hint: glimpse)

Back to analysis

Fix data types

Let’s take another look at the data types of our dataframe.

glimpse(df)
Observations: 40,752
Variables: 28
$ host_id                     <int> 58306608, 124280354, 124280354, 124280354, 124280354, 1242...
$ host_since                  <chr> "2/11/2016", "4/4/2017", "4/4/2017", "4/4/2017", "4/4/2017...
$ host_response_time          <chr> "within an hour", "within an hour", "within an hour", "wit...
$ host_response_rate          <chr> "0.78", "0.99", "0.99", "0.99", "0.99", "0.99", "0.95", "N...
$ host_listings_count         <int> 1, 7, 7, 7, 7, 7, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1...
$ neighbourhood               <chr> "Hell's Kitchen", "Hell's Kitchen", "Hell's Kitchen", "Hel...
$ borough                     <chr> "Manhattan", "Manhattan", "Manhattan", "Manhattan", "Manha...
$ zip_code                    <int> 10000, 10001, 10001, 10001, 10001, 10001, 10001, 10001, 10...
$ property_type               <chr> "Apartment", "Apartment", "Apartment", "Apartment", "Apart...
$ room_type                   <chr> "Private room", "Shared room", "Shared room", "Shared room...
$ accommodates                <int> 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 1, 2, 1, 1, 2, 2, 1, 1...
$ bathrooms                   <dbl> 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 3.0, 1.0, 1.0, 1.0, 1.0...
$ bedrooms                    <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1...
$ beds                        <int> 1, 1, 1, 1, 1, 1, 2, 6, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1...
$ amenities                   <chr> "{TV,Internet,\"Wireless Internet\",\"Air conditioning\",P...
$ price                       <dbl> 200, 28, 39, 39, 39, 43, 50, 50, 50, 53, 54, 55, 55, 60, 6...
$ calendar_updated            <chr> "never", "5 days ago", "5 days ago", "3 days ago", "6 days...
$ number_of_reviews           <int> 0, 0, 2, 1, 0, 3, 185, 0, 0, 0, 21, 5, 3, 12, 0, 20, 1, 1,...
$ review_scores_rating        <int> NA, NA, 100, 100, NA, 87, 93, NA, NA, NA, 96, 100, 80, 92,...
$ review_scores_accuracy      <int> NA, NA, 10, 10, NA, 9, 9, NA, NA, NA, 10, 10, 10, 9, NA, 1...
$ review_scores_cleanliness   <int> NA, NA, 10, 10, NA, 8, 9, NA, NA, NA, 10, 8, 8, 9, NA, 9, ...
$ review_scores_checkin       <int> NA, NA, 10, 10, NA, 9, 10, NA, NA, NA, 10, 10, 10, 9, NA, ...
$ review_scores_communication <int> NA, NA, 10, 10, NA, 9, 10, NA, NA, NA, 10, 10, 10, 9, NA, ...
$ review_scores_location      <int> NA, NA, 10, 10, NA, 9, 10, NA, NA, NA, 10, 10, 9, 10, NA, ...
$ review_scores_value         <int> NA, NA, 10, 10, NA, 9, 10, NA, NA, NA, 10, 10, 8, 9, NA, 1...
$ cancellation_policy         <chr> "moderate", "flexible", "flexible", "moderate", "flexible"...
$ listing_url                 <chr> "https://www.airbnb.com/rooms/18292044", "https://www.airb...
$ description                 <chr> "NBA player and China star Huge residence apartment, Hugel...

Some of the data types don’t look right. Let’s correct them.

Discuss: why is it important to have the correct data type?

But, first, let’s learn how to reference a column in a dataframe: the name of the dataframe followed by $ and then the column name. For example: df$cancellation_policy or df_babynames$Gender.

To change data type, we apply the relevant function, such as.character or as.factor, to a column and then assign the output of the function (values with changed data type) to the same column.

df$host_id <- as.character(df$host_id)
df$host_response_rate <- as.double(df$host_response_rate)
NAs introduced by coercion
df$property_type <- as.factor(df$property_type)
df$host_since <- mdy(df$host_since)

Side note: The last function mdy is different from others. Normally, working with date-time in R (and in prgramming in general) is not an easy task. The lubridate library, which we loaded earlier, makes the job much easier. We’ll not go into the details of date-times. What we did here with mdy is convert character data into date data. We used mdy because the character data were in the form month-date-year. If they were in the form ,for example, year-month-date, we would’ve used the function ymd.

Exercise: Change the data type of any other variables you think necessary.

Missing values

Some values will almost inevitably be missing in a medium to large dataset. Let’s inspect our data for missing values using the function summary, which will give us plenty of other important information.

summary(df)
   host_id            host_since         host_response_time host_response_rate host_listings_count
 Length:40752       Min.   :2008-03-04   Length:40752       Min.   :0.030      Min.   :  0.000    
 Class :character   1st Qu.:2013-01-28   Class :character   1st Qu.:0.940      1st Qu.:  1.000    
 Mode  :character   Median :2014-07-18   Mode  :character   Median :1.000      Median :  1.000    
                    Mean   :2014-05-11                      Mean   :0.934      Mean   :  2.221    
                    3rd Qu.:2015-10-20                      3rd Qu.:1.000      3rd Qu.:  2.000    
                    Max.   :2017-05-02                      Max.   :1.000      Max.   :855.000    
                    NA's   :251                             NA's   :13791      NA's   :251        
 neighbourhood        borough             zip_code         property_type    room_type        
 Length:40752       Length:40752       Min.   :10000   Apartment  :34980   Length:40752      
 Class :character   Class :character   1st Qu.:10023   House      : 3393   Class :character  
 Mode  :character   Mode  :character   Median :11101   Loft       :  875   Mode  :character  
                                       Mean   :10643   Townhouse  :  562                     
                                       3rd Qu.:11220   Condominium:  357                     
                                       Max.   :11694   Other      :  209                     
                                       NA's   :614     (Other)    :  376                     
  accommodates      bathrooms        bedrooms           beds         amenities        
 Min.   : 1.000   Min.   :0.000   Min.   : 0.000   Min.   : 1.000   Length:40752      
 1st Qu.: 2.000   1st Qu.:1.000   1st Qu.: 1.000   1st Qu.: 1.000   Class :character  
 Median : 2.000   Median :1.000   Median : 1.000   Median : 1.000   Mode  :character  
 Mean   : 2.789   Mean   :1.126   Mean   : 1.149   Mean   : 1.534                     
 3rd Qu.: 4.000   3rd Qu.:1.000   3rd Qu.: 1.000   3rd Qu.: 2.000                     
 Max.   :16.000   Max.   :8.000   Max.   :10.000   Max.   :16.000                     
                  NA's   :159     NA's   :69       NA's   :79                         
     price         calendar_updated   number_of_reviews review_scores_rating
 Min.   :   10.0   Length:40752       Min.   :  0.00    Min.   : 20         
 1st Qu.:   70.0   Class :character   1st Qu.:  1.00    1st Qu.: 90         
 Median :  100.0   Mode  :character   Median :  4.00    Median : 95         
 Mean   :  145.3                      Mean   : 16.41    Mean   : 93         
 3rd Qu.:  170.0                      3rd Qu.: 18.00    3rd Qu.:100         
 Max.   :10000.0                      Max.   :432.00    Max.   :100         
                                                        NA's   :9924        
 review_scores_accuracy review_scores_cleanliness review_scores_checkin
 Min.   : 2.000         Min.   : 2.000            Min.   : 2.000       
 1st Qu.: 9.000         1st Qu.: 9.000            1st Qu.:10.000       
 Median :10.000         Median :10.000            Median :10.000       
 Mean   : 9.532         Mean   : 9.196            Mean   : 9.709       
 3rd Qu.:10.000         3rd Qu.:10.000            3rd Qu.:10.000       
 Max.   :10.000         Max.   :10.000            Max.   :10.000       
 NA's   :10017          NA's   :9985              NA's   :10036        
 review_scores_communication review_scores_location review_scores_value cancellation_policy
 Min.   : 2.000              Min.   : 2.000         Min.   : 2.000      Length:40752       
 1st Qu.:10.000              1st Qu.: 9.000         1st Qu.: 9.000      Class :character   
 Median :10.000              Median :10.000         Median :10.000      Mode  :character   
 Mean   : 9.738              Mean   : 9.418         Mean   : 9.338                         
 3rd Qu.:10.000              3rd Qu.:10.000         3rd Qu.:10.000                         
 Max.   :10.000              Max.   :10.000         Max.   :10.000                         
 NA's   :9986                NA's   :10037          NA's   :10039                          
 listing_url        description       
 Length:40752       Length:40752      
 Class :character   Class :character  
 Mode  :character   Mode  :character  
                                      
                                      
                                      
                                      

There are a few options for dealing with missing values.

  • We can drop all rows that have one or more missing values.
  • Drop rows that have missing values in particular columns.
  • Replace the missing values with some other value.
  • Do nothing, but do remember to take care of them when running arithmetic operations. (explained later)

In our case, we’ll go with the last option.

Discuss: Is the last option the best for our dataset? What would be a better alternative?

Data manipluation and exploration

Finally, we’re ready to dive into the data. We’ll use six functions — think of them as six verbs — to slice and dice the data in all kinds of ways. These functions come from the dplyr library. They are:

  • filter
  • select
  • mutate
  • arrange
  • summarize
  • group_by

We’ll first learn these functions one by one and later we’ll learn how to combine them for powerful analysis.

For each of these functions, we provide the name of the dataframe as the first parameter. The subsequent parameters inform what the function is to do with the dataframe.

filter

You use filter, when you want a subset of the rows based on one or more conditions. The returned rows will be those that meet the condition(s).

The syntax is: filter(name_of_dataframe, condition1, condition2, ...)

Use these logical operators to create the conditions:

< less than <= less than or equal to > greater than >= greater than or equal to == exactly equal to != not equal to !x Not x x | y x OR y x & y x AND y

Example: Return a dataframe with only those rows where the neighbourhood is Chelsea.

filter(df, neighbourhood == "Chelsea")

Example: Return a dataframe with only those rows where the price is more than 5000.

You can combine multiple conditions.

Example: Return a dataframe with rows where borough is Manhattan and price is less than 50.

Example: Return a dataframe with rows where cancellation_policy is flexible or accommodates more than 2.

Example: Return a dataframe with rows where number_of_reviews is not 0

filter(df, number_of_reviews != 0)

Note: when multiple conditions are separated by commas are assumed to have and logical operator. Using & instead would be the same thing. For or and other logical operators, we have to explicitly use the appropriate logical operator (e.g. | for or).

Exercise: Return a dataframe with rows where number of bedrooms is not 1 and property_type is House

Exercise: Return a dataframe with rows where host_response_time is within an hour or host_response_rate is more than 90% or calendar was updated today

select

Our second verb is select, which is used to return a subset of the columns.

The syntax is: select(name_of_dataframe, name_of_column1, name_of_column2, ...)

There are some variations to this syntax, which come in handy in certain situations.

  • If you want all columns except one: select(name_of_dataframe, -column_to_exclude)
  • If you want all columns except two: select(name_of_dataframe, -c(column_to_exlude1, column_to_exclue2))
  • If you want the third through eigth columns: select(name_of_dataframe, 3:8)

Example: Return a dataframe with only the listing_url column

Example: Return a dataframe with columns host_response_time, cancellation_policy, and neighbourhood

Example: Return a dataframe with second through fourth columns

Exercise: Return a dataframe with property_type, room_type, and zip_code columns.

Exercise: Return a dataframe with the last 23 columns (sixth through 28th)

Exercise: What would be another way of getting the same dataframe?

Detour: piping

Before I introduce the third verb, let’s take another detour.

You can’t accomplish a lot with filter and select in isolation. Let’s combine them.

Here, we’re creating a new dataframe df_expensive by using filter and then we’re selecting some columns of interest from that dataframe.

Discuss: More than $8000 per night? What’s going on here? How can we find out more? What should we do we this kind of outliers?

The above code could be written more succinctly by using pipe %>%.

df %>% 
  filter(price > 8000) %>% 
  select(price, neighbourhood, description, listing_url)

Let’s try to understand how the pipe works.

filter and select both receive a dataframe as their first parameter (this is true for the other four main verbs as well.) Also, you may have noticed that filter and select return a dataframe (again, it’s true for the others as well). Thanks to this, we can use the pipe to string together several verbs to form a complex query.

filter(df, price > 8000)

is the same as

df %>% filter(price > 8000)

Also,

select(df, price, review_scores_rating, listing_url)

is the same as

df %>% select(price, review_scores_rating, listing_url)

The left side of the pipe is used as the first parameter of the function on the right side of the pipe.

This is how filter, select, and such functions receive a dataframe, does something to it, returns the modified dataframe and passes it on to the next function.

Here’s again the piped expression. Make sure you understand what’s going on.

df %>% 
  filter(price > 8000) %>% 
  select(price, review_scores_rating, listing_url)

arrange

arrange is our third verb. (Note that I’m using verb and function interchangeably.) arrange sorts a dataframe based on the values of one or more columns.

Example: Get the same dataframe as the last code chunk. Sort it by price.

df %>% 
  filter(price > 8000) %>% 
  select(price, neighbourhood, listing_url) %>% 
  arrange(price)

Example: The same dataframe, but now in descending order of price.

df %>% 
  filter(price > 8000) %>% 
  select(price, neighbourhood, listing_url) %>% 
  arrange(desc(price))

Example: You can add more columns to arrange. Now when price is the same, the order will be based on neighbourhood.

df %>% 
  filter(price > 8000) %>% 
  select(price, neighbourhood, listing_url) %>% 
  arrange(desc(price), desc(neighbourhood))

Exercise: Return the whole dataframe df, in descending order of number of bedrooms and where number of bedrooms are equal, descending order of review_scores_rating

mutate

mutate creates a new column by modifying one or more existing columns. It’s better explained by examples.

Example:

Exercise: Create a new review_rating column, which is an average of review_scores_accuracy and review_scores_value.

summarize (or summarise)

summarize, as you’d expect, gives some kind of summary (such as mean, median, min, max) of the values of a given column.

Example: What is the total number of people that can stay in NYC Airbnbs?

df %>% 
  summarize(total_accommodates = sum(accommodates))

Here, total_accommodates is a name I’ve given to the summary number.

The common summary functions are: - sum - n (count) - mean - median - sd (standard deviation) - var (variance) - range - min - max

Note: The function n doesn’t need a parameter, because it just counts the number of rows in the dataframe. This would become more clear in the examples.

We can also write our own function and use here. But that is a more advanced topic.

Example: What is the mean rating for accuracy?

summarize(df, mean_accuracy = mean(review_scores_accuracy))

This did not give us what we expected. This is because any arithmetic operation involving NA (that, not available or missing values) results in NA.

5 + NA

Or,

NA / 33

The review_scores_accuracy had several NAs. The correct way of doing this would be:

summarize(df, mean_accuracy = mean(review_scores_accuracy, na.rm = TRUE))

na.rm = TRUE means “remove all NAs before performing arithmetic operations.”

When we summed the accommodates column earlier, we didn’t add na.rm = TRUE because that column didn’t have any NAs.

Example: We can have multiple summaries at the same time.

summarize(df, min_score_value = min(review_scores_value, na.rm = TRUE), 
          max_score_value = max(review_scores_value, na.rm = TRUE))

Example: What’s the total number of listings in this dataset?

Exercise: The host who’s been with Airbnb NYC for the longest time started on which date?

Exercise: Find the standard deviation and mean of the price variable.

Exercise: Count the number of unique zip_codes in the data. Hint distinct and n.

Exercise: How many listings have perfect 100 for review_scores_rating (which is the overall rating shown as stars on the listing’s webpage)?

Exercise: How many listings have perfect 100 for review_scores_rating and price less than 50?

group-by

group-by is one of the most useful functions. It divides the data into different groups and then summarize summarizes each of the group. This way we can compre the different groups on various attributes.

Example: What’s the mean and median price in each of the five boroughs?

df %>% 
  group_by(borough) %>% 
  summarize(mean(price), median(price))

Exercise: Return the same dataframe but sorted by mean price.

Example: Which are the top 10 neighbourhoods by total number of listings?

df %>% 
  group_by(neighbourhood) %>% 
  summarize(count = n()) %>% 
  arrange(desc(count)) %>% 
  top_n(10)

We introduce a new function top_n here, which is self-explanatory.

Exercise: Do the same as above but only for listings where price is more than 500.

Example: In Manhattan, which neighbourhood has the highest mean price?*

Exercise: In Bronx and Queens, what’s the mean price for different property_type?

*Exercise: Which combination of property_type and room_type has the highest median price?

Here are some excellent resources if you want to keep learning R:

LS0tDQp0aXRsZTogIkEgU3VydmV5IG9mIE5ldyBZb3JrIENpdHkncyBBaXJibmIgTGlzdGluZ3MiDQphdXRob3I6ICJBc2lmIE1laGVkaSINCmRhdGU6ICdgciBTeXMuRGF0ZSgpYCcNCm91dHB1dDoNCiAgd29yZF9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICBodG1sX25vdGVib29rOg0KICAgIGhpZ2hsaWdodDogaGFkZG9jaw0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICB0b2M6IHllcw0Kc3VidGl0bGU6ICdSIGZvciBiZWdpbm5lcnM6IGEgZGF0YSBhbmFseXNpcyB3b3Jrc2hvcCcNCi0tLQ0KDQojIEdvYWxzDQoNCi0gTGVhcm4gdG8gdGhpbmsgbGlrZSBhIGRhdGEgYW5hbHlzdA0KLSBMZWFybiB0aGUgYmFzaWNzIG9mIFIgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UNCi0gTGVhcm4gc29tZSBwb3dlcmZ1bCB0ZWNobmlxdWVzIGZvciBhbmFseXppbmcgYW5kIGV4cGxvcmluZyBhIGRhdGFzZXQNCg0Kd2UnbGwgZG8gdGhlc2UgYnkgdGFraW5nIGEgY2xvc2UgbG9vayBhdCBzb21lIGludGVyZXN0aW5nIGRhdGE6IEFpcmJuYiBsaXN0aW5ncyBmb3IgTmV3IFlvcmsgQ2l0eSBpbiBNYXkgMjAxNy4NCg0KSXQncyBva2F5IGlmIHlvdSBjYW4ndCBzdGFydCB1c2luZyB0aGVzZSB0ZWNobmlxdWVzIHJpZ2h0IGF3YXkgYWZ0ZXIgdGhlIHdvcmtzaG9wLiBUbyBiZSBjb21mb3J0YWJsZSB3aXRoIGEgdG9vbCBsaWtlIFIgdGFrZXMgc29tZSB0aW1lIGFuZCBwcmFjdGljZS4gQnV0IHdlIGRvIGhvcGUgdGhhdCB5b3VyIGV4cG9zdXJlIHRvZGF5IHRvIHRoZXNlIHRvb2xzIGFuZCB0ZWNobmlxdWVzIHdpbGwgb3BlbiB5b3VyIGV5ZXMgYWJvdXQgcG9zc2liaWxpdGllcyBhbmQgbW90aXZhdGUgeW91IHRvIGtlZXAgbGVhcm5pbmcuDQoNCiMgUlN0dWRpbw0KDQpSU3R1ZGlvIGlzIGEgZnJlZSBwcm9ncmFtIHRoYXQgbWFrZXMgd3JpdGluZyBSIGNvZGUgbXVjaCBtb3JlIGVuam95YWJsZSBhbmQgZWZmaWNpZW50Lg0KDQpJdCBoYXMgZm91ciBtYWluIHBhbmVzLiBUaGlzIG9uZSBpcyB0aGUgY29kZSBlZGl0b3IgKGFsc28ga25vd24gYXMgc2NyaXB0IGVkaXRvciBhbmQgc291cmNlIGVkaXRvcikuIFRoaXMgaXMgd2hlcmUgd2UnbGwgc3BlbmQgbW9zdCBvZiBvdXIgdGltZS4gV2UnbGwgc2F5IG1vcmUgYWJvdXQgdGhlIG90aGVyIHBhbmVzIHdoZW4gdGhlIHRpbWUgY29tZXMuIA0KDQojIFIgTm90ZWJvb2sNCg0KQmVmb3JlIHN0YXJ0aW5nIHRoZSBhbmFseXNpcywgbGV0J3MgdW5kZXJzdGFuZCBob3cgdGhpcyBkb2N1bWVudCB3b3Jrcy4gSSdtIGFzc3VtaW5nIHlvdSdyZSByZWFkaW5nIHRoaXMgb24gUlN0dWRpby4gDQoNClRoaXMgaXMgYW4gUiBOb3RlYm9vay4gQW4gUiBOb3RlYm9vayBjb250YWlucyBjb21tZW50YXJ5IGludGVyc3BlcnNlZCB3aXRoIGNvZGUgY2h1bmtzLiBUaGUgY29kZSBjaHVua3MgY2FuIGJlIHJ1biAoZXhlY3V0ZWQpIGluZGVwZW5kZW50bHkgYW5kIGludGVyYWN0aXZlbHkuIFRoZSBvdXRwdXQgd2lsbCBhcHBlYXIgYmVsb3cgdGhlIGNvZGUgY2h1bmsuIFIgTm90ZWJvb2tzIGFyZSBlYXN5IHRvIGNvbnZlcnQgdG8gd2VsbC1mb3JtYXR0ZWQgZmluYWwgZG9jdW1lbnRzIGFzIGEgcGRmIGZpbGUsIGEgd2VicGFnZSwgb3IgYW4gTVMgV29yZCBmaWxlLiBbTW9yZSBoZXJlXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tL3Jfbm90ZWJvb2tzLmh0bWwpDQoNCkNsaWNrIG9uIHRoZSBgcHJldmlld2AgYnV0dG9uIGFib3ZlIHRvIGdldCBhbiBpZGVhLiANCg0KV2hhdCB5b3UncmUgcmVhZGluZyBpcyB0aGUgY29tbWVudGFyeSBhbmQgd2hhdCB5b3Ugc2VlIGJlbG93IGlzIHRoZSBjb250YWluZXIgZm9yIGEgY29kZSBjaHVuay4NCg0KYGBge3J9DQojIFRoZSBjb2RlIGdvZXMgaGVyZQ0KYGBgDQoNClRvIHJ1biBhIGNvZGUgY2h1bmssIGNsaWNrIG9uIHRoZSBsaXR0bGUgdHJpYW5nbGUgYXQgdGhlIHJpZ2h0IGVkZ2Ugb2YgdGhlIGNvZGUgY2h1bmsuIE9yLCB1c2UgdGhlIGtleWJvYXJkIHNob3J0Y3V0ICoqQ1RSTCArIFNISUZUICsgRU5URVIqKiAoV2luZG93cykgb3IgKipDTUQgKyBTSElGVCArIEVOVEVSKiogKE1hYykuDQoNCipFeGVyY2lzZTogUnVuIHRoZSBjaHVuayBiZWxvdyBhbmQgc2VlIHdoYXQgaGFwcGVucy4qDQpgYGB7cn0NCmEgPC0gNSArIDcNCnByaW50KGEpDQpgYGANCg0KQXMgeW91IHNlZSwgdGhlIG91dHB1dCBhcHBlYXJzIHVuZGVyIHRoZSBjb2RlLiBUaGlzIHdheSB5b3UgY2FuIGltbWVkaWF0ZWx5IHNlZSB0aGUgcmVzdWx0IG9mIHRoZSBjb2RlIHlvdSB3cml0ZSBhdCBhbGwgc3RlcHMgb2YgdGhlIGFuYWx5c2lzLg0KDQooRG8geW91IGtub3cgd2hhdCdzIGdvaW5nIG9uIGluIHRoZSBjb2RlIGFib3ZlPyBXZSdsbCB0YWxrIGFib3V0IHZhcmlhYmxlcyBhbmQgYXNzaWdubWVudHMgbGF0ZXIuKQ0KDQpGZWVsIGZyZWUgdG8gYWRkIHlvdXIgY29tbWVudGFyeSBhbmQgY29kZSBhbnl3aGVyZSBpbiB0aGlzIGRvY3VtZW50LiBZb3UgY2FuIGFsd2F5cyBkb3dubG9hZCB0aGUgdW5tb2RpZmllZCB2ZXJzaW9uIGZyb20gW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9hc2lmbS90ZWNoLXdvcmtzaG9wcy90cmVlL21hc3Rlci9EYXRhc2V0cykuIA0KDQpUbyB3cml0ZSB5b3VyIG93biBjb2RlIGNodW5rLCBsb29rIGZvciB0aGUgKippbnNlcnQqKiBidXR0b24gYWJvdmUgYW5kIHRoZW4gc2VsZWN0ICoqUioqLiBPciwgdXNlIHRoZSBrZXlib2FyZCBzaG9ydGN1dCAqKkNUUkwgKyBBTFQgKyBJKiogKFdpbmRvd3MpIG9yICoqQ01EICsgT1BUSU9OICsgSSoqIChNYWMpLiANCg0KKkV4ZXJjaXNlOiBQbGFjZSB0aGUgY3Vyc29yIGJlbG93IGFuZCBjcmVhdGUgYSBjb250YWluZXIgZm9yIHdyaXRpbmcgY29kZS4gTm93IHdyaXRlIHNvbWUgY29kZSBhbmQgcnVuIGl0LiBGb3IgZXhhbXBsZSwgZmluZCB0aGUgcmVzdWx0IG9mIGAzNTQzIC8gNTYyYC4qDQoNCg0KDQoNCiMgVGhlIGRhdGENCg0KVGhlIGRhdGFzZXQgY29udGFpbnMgYWxtb3N0IGFsbCB0aGUgbGlzdGluZ3MgaW4gTllDIGluIE1heSAyMDE3LlRoZSBkYXRhIGNhbWUgZnJvbSBbSW5zaWRlIEFpcmJuYl0oaHR0cDovL2luc2lkZWFpcmJuYi5jb20vZ2V0LXRoZS1kYXRhLmh0bWwpLg0KDQpJdCdzIGFsd2F5cyBhIGdvb2QgaWRlYSB0byBhcHByb2FjaCBhIG5ldyBkYXRhc2V0IHdpdGggYSBmZXcgYmFzaWMgcXVlc3Rpb25zLiBTb21lIGV4YW1wbGVzOg0KDQotIFdoYXQga2luZCBvZiBkYXRhIGlzIGhlcmU/IFdoYXQgYXJlIHRoZSB2YXJpYWJsZXM/IEhvdyBtYW55IG9ic2VydmF0aW9ucz8NCi0gV2hvIGNvbGxlY3RlZCB0aGVzZSBkYXRhPyBIb3c/IFdoeT8NCi0gSG93IGFjY3VyYXRlIGFyZSB0aGVzZSBkYXRhPw0KLSBIb3cgY29tcGxldGUgYXJlIHRoZXNlIGRhdGE/IEFyZSB0aGVyZSB0b28gbWFueSBtaXNzaW5nIHZhbHVlcz8NCi0gQXJlIHRoZXJlIGFub21hbGllcyB0aGF0IEkgbmVlZCB0byBiZSBhd2FyZSBvZj8NCi0gRG8gSSBoYXZlIHRoZSBsZWdhbCByaWdodHMgdG8gdXNlIHRoZXNlIGRhdGE/IFVuZGVyIHdoYXQgY29uZGl0aW9ucz8NCg0KW0hlcmUncyBzb21lIGJhY2tncm91bmQgaW5mb3JtYXRpb25dKGh0dHA6Ly9pbnNpZGVhaXJibmIuY29tL2JlaGluZC5odG1sKSB0aGF0IG1heSBhbnN3ZXIgc29tZSBvZiB0aGVzZSBxdWVzdGlvbnMuDQoNCkxldCdzIHRha2UgYSBsb29rIGF0IHRoZSBkYXRhLiBPcGVuIHRoZSBjc3YgZmlsZSBpbiBFeGNlbC4gQnJvd3NlIGFyb3VuZCBhIGJpdC4gQW55IG9ic2VydmF0aW9ucyBvciBxdWVzdGlvbnM/DQoNCipEaXNjdXNzOiBDYW4geW91IGV4cGxhaW4gd2hhdCBlYWNoIGNvbHVtbiBpcyBhYm91dD8qDQoNCipEaXNjdXNzOiBBcmUgdGhlcmUgYW55IGRhdGEgdGhhdCB5b3UnZCBsaWtlIHRvIGhhdmUgYnV0IG5vdCB0aGVyZT8qDQoNCipEaXNjdXNzOiBXaGF0J3MgdGhlIGZpcnN0IHRoaW5nIHlvdSdkIGxpa2UgdG8gZmluZCBvdXQgZnJvbSB0aGVzZSBkYXRhPyoNCg0KKkRpc2N1c3M6IFdoYXQgZG8geW91IHRoaW5rIGFib3V0IHRoZSBxdWFsaXR5IG9mIHRoZSBkYXRhPyBXaHk/Kg0KDQoNCg0KDQojIFRoZSBjb250ZXh0DQoNCkRhdGEgYW5hbHlzaXMgaGFwcGVucyB3aXRoaW4gYSBsYXJnZXIgY29udGV4dC4gVXN1YWxseSB0aGVyZSdzIGFuIG92ZXJhcmNoaW5nIGJ1c2luZXNzLCBwb2xpY3ksIG9yIHNjaWVudGlmaWMgcXVlc3Rpb24gdGhhdCBvbmUgd291bGQgbGlrZSB0byBhbnN3ZXIuIE9mdGVuIHRoYXQgcXVlc3Rpb24gaXMgbm90IHZlcnkgY2xlYXIuIFdoYXRldmVyIHRoZSBjYXNlLCB5b3UgbmVlZCB0byBhcHByb2FjaCB0aGUgZGF0YSB3aXRoIGN1cmlvc2l0eSBhYm91dCB0aGUgbGFyZ2VyIGNvbnRleHQuICANCg0KVGhlIG1vcmUgeW91IHVuZGVyc3RhbmQgdGhlIGNvbnRleHQgb2YgdGhlIGRhdGEsIHRoZSBiZXR0ZXIgd2lsbCBiZSB5b3VyIHF1ZXN0aW9ucyBhbmQgaHlwb3RoZXNlcyBndWlkaW5nIHlvdXIgYW5hbHlzaXMuDQoNCkZvciBvdXIgZGF0YXNldCwgd2Ugc2hvdWxkIGhhdmUgYSBnb29kIHVuZGVyc3RhbmRpbmcgb2YgdGhlIGJ1c2luZXNzIG1vZGVsIG9mIEFpcmJuYiBhcyB3ZWxsIGFzIHRoZSBlY29ub215IGFuZCBnZW9ncmFwaHkgb2YgTllDLiANCg0KKkRpc2N1c3M6IElzIHRoZXJlIGFueXRoaW5nIGVsc2Ugd2Ugc2hvdWxkIGtub3cgYWJvdXQ/Kg0KDQojIyBBaXJibmINCg0KKkRpc2N1c3M6IEhvdyBtdWNoIGRvIHdlIG5lZWQgdG8ga25vdyBhYm91dCBBaXJibmIgYW5kIGl0cyBidXNpbmVzcz8gSXMgbXkgcGVyc29uYWwgZXhwZXJpZW5jZSB3aXRoIEFpcmJuYiBlbm91Z2g/IFdoYXQgaWYgSSBkb24ndCBoYXZlIGFueSBwZXJzb25hbCBleHBlcmllbmNlPyoNCg0KW0hvdyBBaXJibmIgd29ya3NdKGh0dHBzOi8vd3d3Lndpa2l3YW5kLmNvbS9lbi9BaXJibmIjL0hvd19pdF93b3JrcykNCg0KW0V4YW1wbGUgYWlyYm5iIGxpc3RpbmddKGh0dHBzOi8vd3d3LmFpcmJuYi5jb20vcm9vbXMvMjE2ODU5ND9zPWZyZmZJWFdTKQ0KDQojIyBOZXcgWW9yayBDaXR5DQoNCldlIGNhbiBzdGFydCB3aXRoIGEgbWFwIG9mIHRoZSBjaXR5Lg0KDQohW2ltZ10oaHR0cHM6Ly9pLmltZ3VyLmNvbS9zZTdmVFlULnBuZykNCg0KVGhlIG1hcCBzaG93cyB0aGUgcmVsYXRpdmUgc2l6ZSBhbmQgbG9jYXRpb24gb2YgdGhlIGZpdmUgYm9yb3VnaHMgb2YgTllDLiANCg0KKkRpc2N1c3M6IFdoYXQgZWxzZSBkbyB3ZSBrbm93IGFib3V0IHRoZXNlIGJvcm91Z2hzPyBXaGF0IGFib3V0IHRoZSBuZWlnaGJvcmhvb2RzIHdpdGhpbiB0aGUgYm9yb3VnaHM/Kg0KDQoNCg0KIyBBbmFseXNpcw0KDQojIyBJbnN0YWxsIGFuZCBsb2FkIGxpYnJhcmllcw0KDQpXZSdsbCBmaXJzdCBpbnN0YWxsIGEgZmV3IGxpYnJhcmllcyB0aGF0IHdlJ2xsIG5lZWQgYXQgZGlmZmVyZW50IHN0YWdlcyBvZiB0aGUgYW5hbHlzaXMuIFRoaXMgbWF5IHRha2UgYSB3aGlsZS4NCg0KYGBge3J9DQojIEZvciBsb2FkaW5nIGRhdGENCmluc3RhbGwucGFja2FnZXMoInJlYWRyIikNCiMgRm9yIGRhdGEgbWFuaXB1bGF0aW9uICh3ZSdsbCBzcGVuZCBtb3N0IHRpbWUgd2l0aCB0aGlzIG9uZSkNCmluc3RhbGwucGFja2FnZXMoImRwbHlyIikNCiMgRm9yIHZpc3VhbGl6YXRpb24NCmluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQ0KIyBGb3Igc3RyaW5nIG1hbmlwdWxhdGlvbg0KaW5zdGFsbC5wYWNrYWdlcygic3RyaW5nciIpDQojIFRvIHdvcmsgd2l0aCBkYXRlIGFuZCB0aW1lDQppbnN0YWxsLnBhY2thZ2VzKCJsdWJyaWRhdGUiKQ0KYGBgDQoNCkFuZCB0aGVuIHdlIGxvYWQgdGhlIGxpYnJhcmllcyBmb3Igb3VyIGN1cnJlbnQgUiBzZXNzaW9uLiBBZnRlciB0aGVzZSBsaWJyYXJpZXMgYXJlIHN1Y2Nlc3NmdWxseSBsb2FkZWQsIGFsbCB0aGUgZnVuY3Rpb25zIGluIHRoZXNlIGxpYnJhcmllcyB3aWxsIGJlIGF2YWlsYWJsZSBmb3Igb3VyIHVzZS4NCg0KYGBge3J9DQpsaWJyYXJ5KHJlYWRyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkobHVicmlkYXRlKQ0KYGBgDQoNCiMjIExvYWQgYW5kIHByZXBhcmUgZGF0YQ0KDQpXZSdsbCB1c2UgdGhlIGZ1bmN0aW9uIGByZWFkX2NzdmAsIHdoaWNoIGNvbWVzIGZyb20gdGhlIGxpYnJhcnkgYHJlYWRyYCwgdG8gbG9hZCBkYXRhIGZyb20gZmlsZSBhbmQgY29udmVydCB0aGUgZGF0YSBpbnRvIGEgZGF0YWZyYW1lLiBBIGRhdGFmcmFtZSBpcyBhIHRhYnVsYXIgZGF0YSBzdHJ1Y3R1cmUsIHdpdGggY29sdW1ucyBhcyB2YXJpYWJsZXMgYW5kIHJvd3MgYXMgb2JzZXJ2YXRpb25zLiBJbiB0aGlzIGNhc2UsIGV4YWN0bHkgYXMgaXQgaXMgaW4gdGhlIGNzdiBmaWxlLg0KDQoqRGlzY3VzczogV2hhdCdhIGNzdiBmaWxlPyBXaGF0IG90aGVyIGZvcm1hdHMgYXJlIG91dCB0aGVyZSBmb3Igc3RvcmluZyBkYXRhPyBIb3cgY2FuIHdlIGxvYWQgZGF0YSBmcm9tIGFuIHVuZmFtaWxpYXIgZm9ybWF0PyoNCg0KYGBge3J9DQpkZiA8LSByZWFkX2NzdigiYWlyYm5iX25ld3lvcmsuY3N2IikNCmBgYA0KDQpGaXJzdCB0aGluZyB0byBjaGVjayBpZiB0aGUgZGF0YSBoYXZlIGJlZW4gc3VjY2Vzc3VsbHkgbG9hZGVkIGFuZCBhc3NpZ25lZCB0byB0aGUgdmFyaWFibGUgKGhlcmUgYGRmYCkuIFdlIGRvbid0IHNlZSBhbnkgZXJyb3Ig4oCUIHdoaWNoIGlzIGEgZ29vZCBzaWduLiBXZSBjYW4gYWxzbyBzZWUgdGhlIHZhcmlhYmxlIGBkZmAgaW4gdGhlIGVudmlyb25tZW50IHBhbmUgKHVzdWFsbHkgdG8gdGhlIHJpZ2h0IG9mIHRoaXMgY29kZSBlZGl0b3IgcGFuZSDigJMgY2xpY2sgb24gIkVudmlyb25tZW50IiBpZiBoaWRkZW4pLg0KDQpOZXh0IHRoaW5nIHRvIGNoZWNrIGlzIHdoZXRoZXIgcmVhZF9jc3Ygd2FzIGFibGUgdG8gY29ycmVjdGx5IGluZmVyIHRoZSBkYXRhIHR5cGUgb2YgZWFjaCB2YXJpYWJsZS4NCg0KVGhlIGZ1bmN0aW9uIGBnbGltcHNlYCB3aWxsIGdpdmUgdXMgYSBiZXR0ZXIgZ2xpbXBzZS4NCmBgYHtyfQ0KZ2xpbXBzZShkZikNCmBgYA0KDQpXaGF0IHlvdSBzZWUgd2l0aGluIHRoZSBhbmd1bGFyIGJyYWNrZXRzIGA8Li4uPmAgbmV4dCB0byBlYWNoIGNvbHVtbiBuYW1lIGlzIHRoZSBkYXRhIHR5cGUgb2YgdGhhdCB2YXJpYWJsZS4gDQoNCkJ1dCB3aGF0IGFyZSBkYXRhIHR5cGVzPyBBbmQsIHdoeSBhcmUgdGhleSBpbXBvcnRhbnQ/IFdoYXQgZXhhY3RseSBpcyBhIHZhcmlhYmxlIGluIHByb2dyYW1taW5nPyBXaGF0IGlzIGEgZnVuY3Rpb24/DQoNCkxldCdzIHRha2UgYSBkZXRvdXIuDQoNCiMgRGV0b3VyOiB2YXJpYWJsZXMsIGRhdGEgdHlwZXMsIGFuZCBmdW5jdGlvbnMgaW4gUg0KDQojIyBWYXJpYWJsZXMgKHByb2dyYW1taW5nKQ0KDQpJbiBwcm9ncmFtbWluZywgYSB2YXJpYWJsZSBzdG9yZXMgc29tZSB2YWx1ZSAobm90IHRvIGJlIGNvbmZ1c2VkIHdpdGggd2hhdCB3ZSBjYWxsIHZhcmlhYmxlIGluIHN0YXRpc3RpY3MpLiBPdXIgY29kZSBjYW4gdGhlbiByZWZlcmVuY2UgdGhlIG5hbWUgb2YgdGhlIHZhcmlhYmxlIGZvciBkaWZmZXJlbnQgcHVycG9zZXMuIA0KDQpXZSB1c2UgdGhlIHN5bWJvbCBgPC1gIChsZXNzIHRoYW4gc2lnbiBmb2xsb3dlZCBieSBoeXBoZW4pIHRvIGFzc2lnbiB2YWx1ZSB0byBhIHZhcmlhYmxlIG5hbWUuIEZvciBleGFtcGxlLCBpbiB0aGUgY29kZSBiZWxvdywgYGZvb2AgYW5kIGBiYXJgIGFyZSB2YXJpYWJsZSBuYW1lcy4gV2UgYXNzaWduZWQgdGhlIHZhbHVlcyBgNDNgIGFuZCBgImtpdHRlbiJgIHRvIGBmb29gIGFuZCBgYmFyYCByZXNwZWN0aXZlbHkuIFlvdSBjYW4gcmVhZCB0aGVtIGFzIGBmb29gIGdldHMgYDQzYCBhbmQgYGJhcmAgZ2V0cyBgImtpdHRlbiJgLg0KDQpgYGB7cn0NCmZvbyA8LSA0Mw0KYmFyIDwtICJraXR0ZW4iDQpwcmludChmb28gKiAxMCkNCmBgYA0KDQpJbXBvcnRhbnQ6IEkndmUgYmVlbiB1c2luZyB0aGUgd29yZCAqdmFyaWFibGUqIHRvIG1lYW4gdHdvIGRpZmZlcmVudCB0aGluZ3MuIEEgdmFyaWFibGUgaW4gcHJvZ3JhbW1pbmcgaXMgd2hhdCBJIGRlc2NyaWJlZCBhYm92ZS4gQSB2YXJpYWJsZSBpbiBzdGF0aXN0aWNzIGlzIGFuIGF0dHJpYnV0ZSBvZiBzb21ldGhpbmcsIHdoYXRldmVyIHRoZSBkYXRhIGlzIGFib3V0LiBGb3IgZXhhbXBsZSwgaWYgdGhlIGRhdGEgaXMgYWJvdXQgcGVvcGxlLCBzb21lIHZhcmlhYmxlcyBjb3VsZCBiZSBhZ2UsIHNleCwgaW5jb21lLCBhZGRyZXNzLiBUaGUgbmFtZXMgb2YgdGhlIGNvbHVtbnMgb2YgYSBkYXRhZnJhbWUgYXJlIHZhcmlhYmxlcyBpbiB0aGUgc3RhdGlzdGljYWwgc2Vuc2UuIFVzdWFsbHksIHRoZSBjb250ZXh0IHdvdWxkIGNsYXJpZnkgd2hpY2ggbWVhbmluZyBpcyBpbnRlbmRlZC4NCg0KVGlwOiBHaXZlIHNob3J0IGRlc2NyaXB0aXZlIG5hbWVzIHRvIHlvdXIgdmFyaWFibGVzLiBUaGlzIHdpbGwgbWFrZSB0aGUgY29kZSBlYXN5IHRvIGZvbGxvdy4NCg0KKkV4ZXJjaXNlOiBhIHJvb20gaXMgMTEgeWFyZHMgbG9uZyBhbmQgNy41IHlhcmRzIHdpZGUuIEFzc2lnbiB0aGVzZSB2YWx1ZXMgdG8gYHdpZHRoYCBhbmQgYGxlbmd0aGAgdmFyaWFibGVzIGFuZCB0aGVuIGNhbGN1bGF0ZSB0aGUgYGFyZWFgIG9mIHRoZSByb29tLiBZb3UgY2FuLCBvZiBjb3Vyc2UsIGdpdmUgZGlmZmVyZW50IG5hbWVzIHRvIHRoZXNlIHZhcmlhYmxlcyBpZiB5b3Ugd2lzaC4qDQoNCmBgYHtyfQ0KDQpgYGANCg0KIyMgRGF0YSB0eXBlcw0KDQpUbyB1bmRlcnN0YW5kIGhvdyBSIHN0b3JlcyBhbmQgaGFuZGxlcyBpbmZvcm1hdGlvbiwgd2UgbmVlZCB0byBrbm93IGEgbGl0dGxlIGFib3V0IGRhdGEgdHlwZXMuDQoNCkJ1dCwgZmlyc3QsIHZlY3Rvcjogb25lIG9mIHRoZSBrZXkgY29uY2VwdHMgaW4gUi4gVGhpbmsgb2YgYSB2ZWN0b3IgYXMgc2ltcGx5IGEgc2VxdWVuY2Ugb2YgZGF0YS4gRm9yIGV4YW1wbGUsIGEgY29sdW1uIGluIGEgZGF0YSB0YWJsZSBpcyBhIHZlY3Rvci4gVGhpcyBpcyBob3cgeW91IGNyZWF0ZSBhIHZlY3RvcjogYGMoZWxlbWVudDEsIGVsZW1lbnQyLCBlbGVtZW50MywgLi4uKWANCg0KYGBge3J9DQp2ZWMgPC0gYygzLCA1LCAxMCwgMjApDQpwcmludCh2ZWMpDQpgYGANCg0KQSB2ZWN0b3IgY2FuIGNvbnRhaW4gZm91ciBtYWluIHR5cGVzIG9mIGRhdGE6DQoNCi0gSW50ZWdlcjogc3VjaCBhcyAyLCA1NDMsIDkwLg0KLSBEb3VibGU6IG51bWJlcnMgb3RoZXIgdGhhbiBpbnRlZ2Vycywgc3VjaCBhcyA0LjU2LCAxLzMzLiBEb3VibGVzIGFyZSBhbHdheXMgYXBwcm94aW1hdGlvbnMuDQotIENoYXJhY3RlcjogYSBzZXF1ZW5jZSBvZiBsZXR0ZXJzLCBudW1iZXJzLCBwdW5jdHVhdGlvbnMsIGV0Yy4sIHN1Y2ggYXMgInJhaW5ib3ciLCAiMzQyNjUiLiBDaGFyYWN0ZXIgZGF0YSBhcmUgYWx3YXlzIHdyaXR0ZW4gd2l0aGluIHF1b3RlcywgZWl0aGVyIHNpbmdsZSBvciBkb3VibGUgKHdlJ2xsIGFsd2F5cyB1c2UgZG91YmxlIGZvciBjb25zaXN0ZW5jeSkuDQotIExvZ2ljYWw6IGRhdGEgdGhhdCBjYW4gdGFrZSBvbiBvbmUgb2Ygb25seSB0d28gdmFsdWVzIOKAlCBUUlVFIG9yIEZBTFNFLg0KDQoqRGlzY3VzczogV2h5IGlzICIzNDI2NSIgb2YgY2hhcmFjdGVyIHR5cGU/IFdoYXQgaXMgYSByZWFsIHdvcmxkIGV4YW1wbGUgb2YgdGhpcyBraW5kIG9mIGRhdGE/Kg0KDQpUaGVyZSBhcmUgdHdvIG90aGVyIGltcG9ydGFudCB3YXlzIHRvIHN0b3JlIGRhdGE6IA0KLSBgZmFjdG9yYCwgZm9yIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyAoaGVyZSAidmFyaWFibGUiIGluIHN0YXRpc3RpY2FsIHNlbnNlKS4gQSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBpcyBvbmUgdGhhdCBjYW4gdGFrZSBvbiBhIGxpbWl0ZWQgZml4ZWQgbnVtYmVyIG9mIHZhbHVlcy4NCi0gYGRhdGUtdGltZWAgb3IganVzdCBgZGF0ZWANCg0KDQojIyBGdW5jdGlvbnMNCg0KVGhlIGNvbmNlcHQgb2YgYSBmdW5jdGlvbiBpcyBleGFjdGx5IHRoZSBzYW1lIGFzIGluIEV4Y2VsLiBZb3UgZ2l2ZSBzb21lIHZhbHVlcyAoY2FsbGVkIHBhcmFtZXRlcnMpIHRvIGEgZnVuY3Rpb24uIFRoZSBmdW5jdGlvbiBkb2VzIHNvbWV0aGluZyB3aXRoIHRoZSB2YWx1ZXMgYW5kIHJldHVybnMgYSBuZXcgdmFsdWUuDQoNCkhlcmUncyBhIHNpbXBsZSBmdW5jdGlvbiB0aGF0IHdlIG5hbWVkIGBhZGQxMGAuIEFsbCBpdCBkb2VzIGlzIGFkZHMgMTAgdG8gYW55IG51bWJlciBnaXZlbiB0byBpdC4NCg0KYGBge3J9DQphZGQxMCA8LSAgZnVuY3Rpb24obnVtKXsNCiAgbmV3X3ZhbHVlIDwtICBudW0gKyAxMA0KICByZXR1cm4gKG5ld192YWx1ZSkNCn0NCmBgYA0KDQpBbmQsIHRoZW4sIHdlIGNhbiB1c2UgdGhpcyBmdW5jdGlvbiB3aGVuZXZlciB3ZSBuZWVkIHRvIGFkZCAxMCB0byBhIG51bWJlciAoT2YgY291cnNlLCB3ZSB3b24ndC4gVGhlcmUgYXJlIGVhc2llciB3YXlzIHRvIGFkZCBhIG51bWJlcikuDQoNCmBgYHtyfQ0KIyBBZGQgMTAgdG8gMzUNCmFkZDEwKDM1KQ0KYGBgDQoNClRvZGF5LCB3ZSB3b24ndCB3cml0ZSBhbnkgZnVuY3Rpb24sIFJhdGhlciwgd2UnbGwgdXNlIGZ1bmN0aW9ucyB3cml0dGVuIGJ5IG90aGVycy4gSGVyZSdzIGFuIGV4YW1wbGU6IHRoZSBgbWVhbmAgZnVuY3Rpb24sIHdoaWNoIGNvbWVzIHByZWxvYWRlZCBpbiBSLg0KDQpgYGB7cn0NCiMgRmlyc3QgbGV0J3MgY3JlYXRlIGEgdmVjdG9yDQp2ZWMyIDwtIGMoMjMsIDUzLCAxMSwgMzQsIDg3LCAxMDAsIDUsIDEyLCA2NiwgOSwgODcsIDExMCwgMjAsIDMzLCA1NCwgNDMsIDc2KQ0KIyBMZXQncyBjYWxjdWxhdGUgdGhlIG1lYW4gb2YgdGhlIHZlY3Rvcg0KY2FsY3VsYXRlZF9tZWFuIDwtIG1lYW4odmVjMikNCiMgVGhlbiBvdXRwdXQgaXQgd2l0aCBzb21lIHRleHQgZGVzY3JpcHRpb24NCm1lc3NhZ2UoIlRoZSBtZWFuIG9mIHRoZSB2ZWN0b3IgdmVjMiBpcyAiLCBjYWxjdWxhdGVkX21lYW4pDQpgYGANCipFeGVyY2lzZTogY2FsY3VsYXRlIHRoZSBzdW0sIG1lZGlhbiwgYW5kIHN0YW5kYXJkIGRldmlhdGlvbiAoaGludDogc2QpIG9mIHRoZSB2ZWN0b3IgdmVjMi4NCg0KYGBge3J9DQoNCmBgYA0KDQpOb3cgdGhhdCB3ZSB1bmRlcnN0YW5kIGRhdGEgdHlwZXMsIGxldCdzIGxvYWQgc29tZSBuZXcgZGF0YS4NCg0KKkV4ZXJjaXNlOiBZb3UnbGwgc2VlIHRoZXJlJ3MgYW5vdGhlciBkYXRhIGZpbGUgaW4gdGhlIGZvbGRlcjogInVzX2JhYnluYW1lcy5jc3YiLiBMb2FkIHRoZSBkYXRhIGZyb20gdGhpcyBmaWxlIHRvIGEgZGF0YWZyYW1lIChoaW50OiByZWFkX2NzdikuIEFzc2lnbiB0aGUgZGF0YWZyYW1lIHRvIGEgdmFyaWFibGUgbmFtZWQgYGRmX2JhYnluYW1lc2AuIFdlJ2xsIGNvbWUgYmFjayB0byB0aGlzIGRhdGFmcmFtZSBsYXRlci4qDQoNCmBgYHtyfQ0KDQpgYGANCg0KKkV4ZXJjaXNlOiBOb3cgdGFrZSBhIGxvb2sgYXQgdGhlIGRhdGFmcmFtZSdzIGRhdGEgdHlwZXMuIChoaW50OiBnbGltcHNlKSoNCg0KYGBge3J9DQoNCmBgYA0KDQojIEJhY2sgdG8gYW5hbHlzaXMNCg0KIyMgRml4IGRhdGEgdHlwZXMNCg0KTGV0J3MgdGFrZSBhbm90aGVyIGxvb2sgYXQgdGhlIGRhdGEgdHlwZXMgb2Ygb3VyIGRhdGFmcmFtZS4NCg0KYGBge3J9DQpnbGltcHNlKGRmKQ0KYGBgDQoNCg0KU29tZSBvZiB0aGUgZGF0YSB0eXBlcyBkb24ndCBsb29rIHJpZ2h0LiBMZXQncyBjb3JyZWN0IHRoZW0uDQoNCipEaXNjdXNzOiB3aHkgaXMgaXQgaW1wb3J0YW50IHRvIGhhdmUgdGhlIGNvcnJlY3QgZGF0YSB0eXBlPyoNCg0KQnV0LCBmaXJzdCwgbGV0J3MgbGVhcm4gaG93IHRvIHJlZmVyZW5jZSBhIGNvbHVtbiBpbiBhIGRhdGFmcmFtZTogdGhlIG5hbWUgb2YgdGhlIGRhdGFmcmFtZSBmb2xsb3dlZCBieSBgJGAgYW5kIHRoZW4gdGhlIGNvbHVtbiBuYW1lLiBGb3IgZXhhbXBsZTogYGRmJGNhbmNlbGxhdGlvbl9wb2xpY3lgIG9yIGBkZl9iYWJ5bmFtZXMkR2VuZGVyYC4NCg0KVG8gY2hhbmdlIGRhdGEgdHlwZSwgd2UgYXBwbHkgdGhlIHJlbGV2YW50IGZ1bmN0aW9uLCBzdWNoIGBhcy5jaGFyYWN0ZXJgIG9yIGBhcy5mYWN0b3JgLCB0byBhIGNvbHVtbiBhbmQgdGhlbiBhc3NpZ24gdGhlIG91dHB1dCBvZiB0aGUgZnVuY3Rpb24gKHZhbHVlcyB3aXRoIGNoYW5nZWQgZGF0YSB0eXBlKSB0byB0aGUgc2FtZSBjb2x1bW4uIA0KDQpgYGB7cn0NCmRmJGhvc3RfaWQgPC0gYXMuY2hhcmFjdGVyKGRmJGhvc3RfaWQpDQpkZiRob3N0X3Jlc3BvbnNlX3JhdGUgPC0gYXMuZG91YmxlKGRmJGhvc3RfcmVzcG9uc2VfcmF0ZSkNCmRmJHByb3BlcnR5X3R5cGUgPC0gYXMuZmFjdG9yKGRmJHByb3BlcnR5X3R5cGUpDQpkZiRob3N0X3NpbmNlIDwtIG1keShkZiRob3N0X3NpbmNlKQ0KYGBgDQpTaWRlIG5vdGU6IFRoZSBsYXN0IGZ1bmN0aW9uIGBtZHlgIGlzIGRpZmZlcmVudCBmcm9tIG90aGVycy4gTm9ybWFsbHksIHdvcmtpbmcgd2l0aCBkYXRlLXRpbWUgaW4gUiAoYW5kIGluIHByZ3JhbW1pbmcgaW4gZ2VuZXJhbCkgaXMgbm90IGFuIGVhc3kgdGFzay4gVGhlIGx1YnJpZGF0ZSBsaWJyYXJ5LCB3aGljaCB3ZSBsb2FkZWQgZWFybGllciwgbWFrZXMgdGhlIGpvYiBtdWNoIGVhc2llci4gV2UnbGwgbm90IGdvIGludG8gdGhlIGRldGFpbHMgb2YgZGF0ZS10aW1lcy4gV2hhdCB3ZSBkaWQgaGVyZSB3aXRoIGBtZHlgIGlzIGNvbnZlcnQgY2hhcmFjdGVyIGRhdGEgaW50byBkYXRlIGRhdGEuIFdlIHVzZWQgYG1keWAgYmVjYXVzZSB0aGUgY2hhcmFjdGVyIGRhdGEgd2VyZSBpbiB0aGUgZm9ybSBtb250aC1kYXRlLXllYXIuIElmIHRoZXkgd2VyZSBpbiB0aGUgZm9ybSAsZm9yIGV4YW1wbGUsIHllYXItbW9udGgtZGF0ZSwgd2Ugd291bGQndmUgdXNlZCB0aGUgZnVuY3Rpb24gYHltZGAuDQoNCipFeGVyY2lzZTogQ2hhbmdlIHRoZSBkYXRhIHR5cGUgb2YgYW55IG90aGVyIHZhcmlhYmxlcyB5b3UgdGhpbmsgbmVjZXNzYXJ5LioNCmBgYHtyfQ0KDQpgYGANCg0KIyMgTWlzc2luZyB2YWx1ZXMNCg0KU29tZSB2YWx1ZXMgd2lsbCBhbG1vc3QgaW5ldml0YWJseSBiZSBtaXNzaW5nIGluIGEgbWVkaXVtIHRvIGxhcmdlIGRhdGFzZXQuIExldCdzIGluc3BlY3Qgb3VyIGRhdGEgZm9yIG1pc3NpbmcgdmFsdWVzIHVzaW5nIHRoZSBmdW5jdGlvbiBgc3VtbWFyeWAsIHdoaWNoIHdpbGwgZ2l2ZSB1cyBwbGVudHkgb2Ygb3RoZXIgaW1wb3J0YW50IGluZm9ybWF0aW9uLg0KDQpgYGB7cn0NCnN1bW1hcnkoZGYpDQoNCmBgYA0KDQoNClRoZXJlIGFyZSBhIGZldyBvcHRpb25zIGZvciBkZWFsaW5nIHdpdGggbWlzc2luZyB2YWx1ZXMuDQoNCi0gV2UgY2FuIGRyb3AgYWxsIHJvd3MgdGhhdCBoYXZlIG9uZSBvciBtb3JlIG1pc3NpbmcgdmFsdWVzLg0KLSBEcm9wIHJvd3MgdGhhdCBoYXZlIG1pc3NpbmcgdmFsdWVzIGluIHBhcnRpY3VsYXIgY29sdW1ucy4NCi0gUmVwbGFjZSB0aGUgbWlzc2luZyB2YWx1ZXMgd2l0aCBzb21lIG90aGVyIHZhbHVlLg0KLSBEbyBub3RoaW5nLCBidXQgZG8gcmVtZW1iZXIgdG8gdGFrZSBjYXJlIG9mIHRoZW0gd2hlbiBydW5uaW5nIGFyaXRobWV0aWMgb3BlcmF0aW9ucy4gKGV4cGxhaW5lZCBsYXRlcikNCg0KSW4gb3VyIGNhc2UsIHdlJ2xsIGdvIHdpdGggdGhlIGxhc3Qgb3B0aW9uLg0KDQoqRGlzY3VzczogSXMgdGhlIGxhc3Qgb3B0aW9uIHRoZSBiZXN0IGZvciBvdXIgZGF0YXNldD8gV2hhdCB3b3VsZCBiZSBhIGJldHRlciBhbHRlcm5hdGl2ZT8qDQoNCiMjIERhdGEgbWFuaXBsdWF0aW9uIGFuZCBleHBsb3JhdGlvbg0KDQpGaW5hbGx5LCB3ZSdyZSByZWFkeSB0byBkaXZlIGludG8gdGhlIGRhdGEuIFdlJ2xsIHVzZSBzaXggZnVuY3Rpb25zIOKAlCB0aGluayBvZiB0aGVtIGFzIHNpeCB2ZXJicyDigJQgdG8gc2xpY2UgYW5kIGRpY2UgdGhlIGRhdGEgaW4gYWxsIGtpbmRzIG9mIHdheXMuIFRoZXNlIGZ1bmN0aW9ucyBjb21lIGZyb20gdGhlIGRwbHlyIGxpYnJhcnkuIFRoZXkgYXJlOg0KDQotIGZpbHRlcg0KLSBzZWxlY3QNCi0gbXV0YXRlDQotIGFycmFuZ2UNCi0gc3VtbWFyaXplDQotIGdyb3VwX2J5DQoNCldlJ2xsIGZpcnN0IGxlYXJuIHRoZXNlIGZ1bmN0aW9ucyBvbmUgYnkgb25lIGFuZCBsYXRlciB3ZSdsbCBsZWFybiBob3cgdG8gY29tYmluZSB0aGVtIGZvciBwb3dlcmZ1bCBhbmFseXNpcy4gDQoNCkZvciBlYWNoIG9mIHRoZXNlIGZ1bmN0aW9ucywgd2UgcHJvdmlkZSB0aGUgbmFtZSBvZiB0aGUgZGF0YWZyYW1lIGFzIHRoZSBmaXJzdCBwYXJhbWV0ZXIuIFRoZSBzdWJzZXF1ZW50IHBhcmFtZXRlcnMgaW5mb3JtIHdoYXQgdGhlIGZ1bmN0aW9uIGlzIHRvIGRvIHdpdGggdGhlIGRhdGFmcmFtZS4NCg0KIyMjIGZpbHRlcg0KDQpZb3UgdXNlIGBmaWx0ZXJgLCB3aGVuIHlvdSB3YW50IGEgc3Vic2V0IG9mIHRoZSByb3dzIGJhc2VkIG9uIG9uZSBvciBtb3JlIGNvbmRpdGlvbnMuIFRoZSByZXR1cm5lZCByb3dzIHdpbGwgYmUgdGhvc2UgdGhhdCBtZWV0IHRoZSBjb25kaXRpb24ocykuDQoNClRoZSBzeW50YXggaXM6IGBmaWx0ZXIobmFtZV9vZl9kYXRhZnJhbWUsIGNvbmRpdGlvbjEsIGNvbmRpdGlvbjIsIC4uLilgDQoNClVzZSB0aGVzZSAqbG9naWNhbCBvcGVyYXRvcnMqIHRvIGNyZWF0ZSB0aGUgY29uZGl0aW9uczoNCiAgDQogIDwJICAgICAgbGVzcyB0aGFuDQogIDw9CSAgICBsZXNzIHRoYW4gb3IgZXF1YWwgdG8NCiAgPgkgICAgICBncmVhdGVyIHRoYW4NCiAgPj0JICAgIGdyZWF0ZXIgdGhhbiBvciBlcXVhbCB0bw0KICA9PQkgICAgZXhhY3RseSBlcXVhbCB0bw0KICAhPQkgICAgbm90IGVxdWFsIHRvDQogICF4CSAgICBOb3QgeA0KICB4IHwgeQkgIHggT1IgeQ0KICB4ICYgeQkgIHggQU5EIHkNCg0KDQoNCkV4YW1wbGU6IFJldHVybiBhIGRhdGFmcmFtZSB3aXRoIG9ubHkgdGhvc2Ugcm93cyB3aGVyZSB0aGUgbmVpZ2hib3VyaG9vZCBpcyBDaGVsc2VhLg0KYGBge3J9DQpmaWx0ZXIoZGYsIG5laWdoYm91cmhvb2QgPT0gIkNoZWxzZWEiKQ0KYGBgDQoNCkV4YW1wbGU6IFJldHVybiBhIGRhdGFmcmFtZSB3aXRoIG9ubHkgdGhvc2Ugcm93cyB3aGVyZSB0aGUgcHJpY2UgaXMgbW9yZSB0aGFuIDUwMDAuDQpgYGB7cn0NCmZpbHRlcihkZiwgcHJpY2UgPiA4MDAwKQ0KYGBgDQoNCllvdSBjYW4gY29tYmluZSBtdWx0aXBsZSBjb25kaXRpb25zLg0KDQpFeGFtcGxlOiBSZXR1cm4gYSBkYXRhZnJhbWUgd2l0aCByb3dzIHdoZXJlIGJvcm91Z2ggaXMgTWFuaGF0dGFuIGFuZCBwcmljZSBpcyBsZXNzIHRoYW4gNTAuDQpgYGB7cn0NCmZpbHRlcihkZiwgYm9yb3VnaCA9PSAiTWFuaGF0dGFuIiwgcHJpY2UgPCAyMCkNCmBgYA0KDQpFeGFtcGxlOiBSZXR1cm4gYSBkYXRhZnJhbWUgd2l0aCByb3dzIHdoZXJlIGNhbmNlbGxhdGlvbl9wb2xpY3kgaXMgZmxleGlibGUgKm9yKiBhY2NvbW1vZGF0ZXMgbW9yZSB0aGFuIDIuDQpgYGB7cn0NCmZpbHRlcihkZiwgY2FuY2VsbGF0aW9uX3BvbGljeSA9PSAiZmxleGlibGUiIHwgYWNjb21tb2RhdGVzID4gMiApDQpgYGANCg0KRXhhbXBsZTogUmV0dXJuIGEgZGF0YWZyYW1lIHdpdGggcm93cyB3aGVyZSBudW1iZXJfb2ZfcmV2aWV3cyBpcyAqbm90KiAwDQpgYGB7cn0NCmZpbHRlcihkZiwgbnVtYmVyX29mX3Jldmlld3MgIT0gMCkNCmBgYA0KDQoNCk5vdGU6IHdoZW4gbXVsdGlwbGUgY29uZGl0aW9ucyBhcmUgc2VwYXJhdGVkIGJ5IGNvbW1hcyBhcmUgYXNzdW1lZCB0byBoYXZlICphbmQqIGxvZ2ljYWwgb3BlcmF0b3IuIFVzaW5nIGAmYCBpbnN0ZWFkIHdvdWxkIGJlIHRoZSBzYW1lIHRoaW5nLiBGb3IgKm9yKiBhbmQgb3RoZXIgbG9naWNhbCBvcGVyYXRvcnMsIHdlIGhhdmUgdG8gZXhwbGljaXRseSB1c2UgdGhlIGFwcHJvcHJpYXRlIGxvZ2ljYWwgb3BlcmF0b3IgKGUuZy4gYHxgIGZvciAqb3IqKS4NCg0KKkV4ZXJjaXNlOiBSZXR1cm4gYSBkYXRhZnJhbWUgd2l0aCByb3dzIHdoZXJlIG51bWJlciBvZiBiZWRyb29tcyBpcyBub3QgMSBhbmQgcHJvcGVydHlfdHlwZSBpcyBIb3VzZSoNCmBgYHtyfQ0KDQpgYGANCg0KKkV4ZXJjaXNlOiBSZXR1cm4gYSBkYXRhZnJhbWUgd2l0aCByb3dzIHdoZXJlIGhvc3RfcmVzcG9uc2VfdGltZSBpcyB3aXRoaW4gYW4gaG91ciBvciBob3N0X3Jlc3BvbnNlX3JhdGUgaXMgbW9yZSB0aGFuIDkwJSBvciBjYWxlbmRhciB3YXMgdXBkYXRlZCB0b2RheSoNCmBgYHtyfQ0KDQpgYGANCg0KIyMjIHNlbGVjdA0KDQpPdXIgc2Vjb25kIHZlcmIgaXMgYHNlbGVjdGAsIHdoaWNoIGlzIHVzZWQgdG8gcmV0dXJuIGEgc3Vic2V0IG9mIHRoZSBjb2x1bW5zLg0KDQpUaGUgc3ludGF4IGlzOiBgc2VsZWN0KG5hbWVfb2ZfZGF0YWZyYW1lLCBuYW1lX29mX2NvbHVtbjEsIG5hbWVfb2ZfY29sdW1uMiwgLi4uKWANCg0KVGhlcmUgYXJlIHNvbWUgdmFyaWF0aW9ucyB0byB0aGlzIHN5bnRheCwgd2hpY2ggY29tZSBpbiBoYW5keSBpbiBjZXJ0YWluIHNpdHVhdGlvbnMuDQoNCi0gSWYgeW91IHdhbnQgYWxsIGNvbHVtbnMgZXhjZXB0IG9uZTogYHNlbGVjdChuYW1lX29mX2RhdGFmcmFtZSwgLWNvbHVtbl90b19leGNsdWRlKWANCi0gSWYgeW91IHdhbnQgYWxsIGNvbHVtbnMgZXhjZXB0IHR3bzogYHNlbGVjdChuYW1lX29mX2RhdGFmcmFtZSwgLWMoY29sdW1uX3RvX2V4bHVkZTEsIGNvbHVtbl90b19leGNsdWUyKSlgDQotIElmIHlvdSB3YW50IHRoZSB0aGlyZCB0aHJvdWdoIGVpZ3RoIGNvbHVtbnM6IGBzZWxlY3QobmFtZV9vZl9kYXRhZnJhbWUsIDM6OClgDQoNCkV4YW1wbGU6IFJldHVybiBhIGRhdGFmcmFtZSB3aXRoIG9ubHkgdGhlIGxpc3RpbmdfdXJsIGNvbHVtbg0KYGBge3J9DQpzZWxlY3QoZGYsIGxpc3RpbmdfdXJsKQ0KYGBgDQoNCkV4YW1wbGU6IFJldHVybiBhIGRhdGFmcmFtZSB3aXRoIGNvbHVtbnMgaG9zdF9yZXNwb25zZV90aW1lLCBjYW5jZWxsYXRpb25fcG9saWN5LCBhbmQgbmVpZ2hib3VyaG9vZA0KYGBge3J9DQpzZWxlY3QoZGYsIGhvc3RfcmVzcG9uc2VfdGltZSwgY2FuY2VsbGF0aW9uX3BvbGljeSwgbmVpZ2hib3VyaG9vZCkNCmBgYA0KRXhhbXBsZTogUmV0dXJuIGEgZGF0YWZyYW1lIHdpdGggc2Vjb25kIHRocm91Z2ggZm91cnRoIGNvbHVtbnMNCmBgYHtyfQ0Kc2VsZWN0KGRmLCAyOjQpDQpgYGANCg0KKkV4ZXJjaXNlOiBSZXR1cm4gYSBkYXRhZnJhbWUgd2l0aCBwcm9wZXJ0eV90eXBlLCByb29tX3R5cGUsIGFuZCB6aXBfY29kZSBjb2x1bW5zLioNCmBgYHtyfQ0KDQpgYGANCg0KDQoqRXhlcmNpc2U6IFJldHVybiBhIGRhdGFmcmFtZSB3aXRoIHRoZSBsYXN0IDIzIGNvbHVtbnMgKHNpeHRoIHRocm91Z2ggMjh0aCkqDQpgYGB7cn0NCg0KYGBgDQoNCipFeGVyY2lzZTogV2hhdCB3b3VsZCBiZSBhbm90aGVyIHdheSBvZiBnZXR0aW5nIHRoZSBzYW1lIGRhdGFmcmFtZT8qDQpgYGB7cn0NCg0KYGBgDQoNCg0KIyMgRGV0b3VyOiBwaXBpbmcNCg0KQmVmb3JlIEkgaW50cm9kdWNlIHRoZSB0aGlyZCB2ZXJiLCBsZXQncyB0YWtlIGFub3RoZXIgZGV0b3VyLiANCg0KWW91IGNhbid0IGFjY29tcGxpc2ggYSBsb3Qgd2l0aCBgZmlsdGVyYCBhbmQgYHNlbGVjdGAgaW4gaXNvbGF0aW9uLiBMZXQncyBjb21iaW5lIHRoZW0uIA0KDQpIZXJlLCB3ZSdyZSBjcmVhdGluZyBhIG5ldyBkYXRhZnJhbWUgYGRmX2V4cGVuc2l2ZWAgYnkgdXNpbmcgYGZpbHRlcmAgYW5kIHRoZW4gd2UncmUgc2VsZWN0aW5nIHNvbWUgY29sdW1ucyBvZiBpbnRlcmVzdCBmcm9tIHRoYXQgZGF0YWZyYW1lLg0KYGBge3J9DQpkZl9leHBlbnNpdmUgPC0gZmlsdGVyKGRmLCBwcmljZSA+IDgwMDApDQpzZWxlY3QoZGZfZXhwZW5zaXZlLCBwcmljZSwgbmVpZ2hib3VyaG9vZCwgbGlzdGluZ191cmwpDQpgYGANCipEaXNjdXNzOiBNb3JlIHRoYW4gJDgwMDAgcGVyIG5pZ2h0PyBXaGF0J3MgZ29pbmcgb24gaGVyZT8gSG93IGNhbiB3ZSBmaW5kIG91dCBtb3JlPyBXaGF0IHNob3VsZCB3ZSBkbyB3ZSB0aGlzIGtpbmQgb2Ygb3V0bGllcnM/Kg0KDQpUaGUgYWJvdmUgY29kZSBjb3VsZCBiZSB3cml0dGVuIG1vcmUgc3VjY2luY3RseSBieSB1c2luZyBwaXBlIGAlPiVgLg0KYGBge3J9DQpkZiAlPiUgDQogIGZpbHRlcihwcmljZSA+IDgwMDApICU+JSANCiAgc2VsZWN0KHByaWNlLCBuZWlnaGJvdXJob29kLCBsaXN0aW5nX3VybCkNCmBgYA0KTGV0J3MgdHJ5IHRvIHVuZGVyc3RhbmQgaG93IHRoZSBwaXBlIHdvcmtzLg0KDQpgZmlsdGVyYCBhbmQgYHNlbGVjdGAgYm90aCByZWNlaXZlIGEgZGF0YWZyYW1lIGFzIHRoZWlyIGZpcnN0IHBhcmFtZXRlciAodGhpcyBpcyB0cnVlIGZvciB0aGUgb3RoZXIgZm91ciBtYWluIHZlcmJzIGFzIHdlbGwuKSBBbHNvLCB5b3UgbWF5IGhhdmUgbm90aWNlZCB0aGF0IGBmaWx0ZXJgIGFuZCBgc2VsZWN0YCByZXR1cm4gYSBkYXRhZnJhbWUgKGFnYWluLCBpdCdzIHRydWUgZm9yIHRoZSBvdGhlcnMgYXMgd2VsbCkuIFRoYW5rcyB0byB0aGlzLCB3ZSBjYW4gdXNlIHRoZSBwaXBlIHRvIHN0cmluZyB0b2dldGhlciBzZXZlcmFsIHZlcmJzIHRvIGZvcm0gYSBjb21wbGV4IHF1ZXJ5Lg0KDQpgYGB7cn0NCmZpbHRlcihkZiwgcHJpY2UgPiA4MDAwKQ0KYGBgDQppcyB0aGUgc2FtZSBhcw0KYGBge3J9DQpkZiAlPiUgZmlsdGVyKHByaWNlID4gODAwMCkNCmBgYA0KDQpBbHNvLA0KYGBge3J9DQpzZWxlY3QoZGYsIHByaWNlLCByZXZpZXdfc2NvcmVzX3JhdGluZywgbGlzdGluZ191cmwpDQpgYGANCmlzIHRoZSBzYW1lIGFzDQpgYGB7cn0NCmRmICU+JSBzZWxlY3QocHJpY2UsIHJldmlld19zY29yZXNfcmF0aW5nLCBsaXN0aW5nX3VybCkNCmBgYA0KDQpUaGUgbGVmdCBzaWRlIG9mIHRoZSBwaXBlIGlzIHVzZWQgYXMgdGhlIGZpcnN0IHBhcmFtZXRlciBvZiB0aGUgZnVuY3Rpb24gb24gdGhlIHJpZ2h0IHNpZGUgb2YgdGhlIHBpcGUuIA0KDQpUaGlzIGlzIGhvdyBgZmlsdGVyYCwgYHNlbGVjdGAsIGFuZCBzdWNoIGZ1bmN0aW9ucyByZWNlaXZlIGEgZGF0YWZyYW1lLCBkb2VzIHNvbWV0aGluZyB0byBpdCwgcmV0dXJucyB0aGUgbW9kaWZpZWQgZGF0YWZyYW1lIGFuZCBwYXNzZXMgaXQgb24gdG8gdGhlIG5leHQgZnVuY3Rpb24uDQoNCkhlcmUncyBhZ2FpbiB0aGUgcGlwZWQgZXhwcmVzc2lvbi4gTWFrZSBzdXJlIHlvdSB1bmRlcnN0YW5kIHdoYXQncyBnb2luZyBvbi4NCmBgYHtyfQ0KZGYgJT4lIA0KICBmaWx0ZXIocHJpY2UgPiA4MDAwKSAlPiUgDQogIHNlbGVjdChwcmljZSwgcmV2aWV3X3Njb3Jlc19yYXRpbmcsIGxpc3RpbmdfdXJsKQ0KYGBgDQoNCiMjIyBhcnJhbmdlDQoNCmBhcnJhbmdlYCBpcyBvdXIgdGhpcmQgdmVyYi4gKE5vdGUgdGhhdCBJJ20gdXNpbmcgdmVyYiBhbmQgZnVuY3Rpb24gaW50ZXJjaGFuZ2VhYmx5LikgYGFycmFuZ2VgIHNvcnRzIGEgZGF0YWZyYW1lIGJhc2VkIG9uIHRoZSB2YWx1ZXMgb2Ygb25lIG9yIG1vcmUgY29sdW1ucy4NCg0KRXhhbXBsZTogR2V0IHRoZSBzYW1lIGRhdGFmcmFtZSBhcyB0aGUgbGFzdCBjb2RlIGNodW5rLiBTb3J0IGl0IGJ5IHByaWNlLg0KYGBge3J9DQpkZiAlPiUgDQogIGZpbHRlcihwcmljZSA+IDgwMDApICU+JSANCiAgc2VsZWN0KHByaWNlLCByZXZpZXdfc2NvcmVzX3JhdGluZywgbGlzdGluZ191cmwpICU+JSANCiAgYXJyYW5nZShwcmljZSkNCmBgYA0KDQpFeGFtcGxlOiBUaGUgc2FtZSBkYXRhZnJhbWUsIGJ1dCBub3cgaW4gZGVzY2VuZGluZyBvcmRlciBvZiBwcmljZS4NCmBgYHtyfQ0KZGYgJT4lIA0KICBmaWx0ZXIocHJpY2UgPiA4MDAwKSAlPiUgDQogIHNlbGVjdChwcmljZSwgcmV2aWV3X3Njb3Jlc19yYXRpbmcsIGxpc3RpbmdfdXJsKSAlPiUgDQogIGFycmFuZ2UoZGVzYyhwcmljZSkpDQpgYGANCkV4YW1wbGU6IFlvdSBjYW4gYWRkIG1vcmUgY29sdW1ucyB0byBgYXJyYW5nZWAuIE5vdyB3aGVuIHByaWNlIGlzIHRoZSBzYW1lLCB0aGUgb3JkZXIgd2lsbCBiZSBiYXNlZCBvbiBuZWlnaGJvdXJob29kLg0KYGBge3J9DQpkZiAlPiUgDQogIGZpbHRlcihwcmljZSA+IDgwMDApICU+JSANCiAgc2VsZWN0KHByaWNlLCBuZWlnaGJvdXJob29kLCBsaXN0aW5nX3VybCkgJT4lIA0KICBhcnJhbmdlKGRlc2MocHJpY2UpLCBuZWlnaGJvdXJob29kKQ0KYGBgDQoqRXhlcmNpc2U6IFJldHVybiB0aGUgd2hvbGUgZGF0YWZyYW1lIGBkZmAsIGluIGRlc2NlbmRpbmcgb3JkZXIgb2YgbnVtYmVyIG9mIGJlZHJvb21zIGFuZCB3aGVyZSBudW1iZXIgb2YgYmVkcm9vbXMgYXJlIGVxdWFsLCBkZXNjZW5kaW5nIG9yZGVyIG9mIHJldmlld19zY29yZXNfcmF0aW5nKg0KYGBge3J9DQoNCmBgYA0KDQojIyMgbXV0YXRlDQoNCmBtdXRhdGVgIGNyZWF0ZXMgYSBuZXcgY29sdW1uIGJ5IG1vZGlmeWluZyBvbmUgb3IgbW9yZSBleGlzdGluZyBjb2x1bW5zLiBJdCdzIGJldHRlciBleHBsYWluZWQgYnkgZXhhbXBsZXMuDQoNCkV4YW1wbGU6DQpgYGB7cn0NCm11dGF0ZShkZiwgcHJpY2VfcGVyX3BlcnNvbiA9IHByaWNlIC8gYWNjb21tb2RhdGVzKQ0KYGBgDQoNCg0KKkV4ZXJjaXNlOiBDcmVhdGUgYSBuZXcgcmV2aWV3X3JhdGluZyBjb2x1bW4sIHdoaWNoIGlzIGFuIGF2ZXJhZ2Ugb2YgcmV2aWV3X3Njb3Jlc19hY2N1cmFjeSBhbmQgcmV2aWV3X3Njb3Jlc192YWx1ZS4qDQoNCmBgYHtyfQ0KDQpgYGANCg0KIyMjIHN1bW1hcml6ZSAob3Igc3VtbWFyaXNlKQ0KDQpgc3VtbWFyaXplYCwgYXMgeW91J2QgZXhwZWN0LCBnaXZlcyBzb21lIGtpbmQgb2Ygc3VtbWFyeSAoc3VjaCBhcyBtZWFuLCBtZWRpYW4sIG1pbiwgbWF4KSBvZiB0aGUgdmFsdWVzIG9mIGEgZ2l2ZW4gY29sdW1uLg0KDQpFeGFtcGxlOiBXaGF0IGlzIHRoZSB0b3RhbCBudW1iZXIgb2YgcGVvcGxlIHRoYXQgY2FuIHN0YXkgaW4gTllDIEFpcmJuYnM/DQpgYGB7cn0NCnN1bW1hcml6ZShkZiwgdG90YWxfYWNjb21tb2RhdGVzID0gc3VtKGFjY29tbW9kYXRlcykpDQpgYGANCkhlcmUsIHRvdGFsX2FjY29tbW9kYXRlcyBpcyBhIG5hbWUgSSd2ZSBnaXZlbiB0byB0aGUgc3VtbWFyeSBudW1iZXIuDQoNClRoZSBjb21tb24gc3VtbWFyeSBmdW5jdGlvbnMgYXJlOg0KLSBzdW0NCi0gbiAoY291bnQpDQotIG1lYW4NCi0gbWVkaWFuDQotIHNkIChzdGFuZGFyZCBkZXZpYXRpb24pDQotIHZhciAodmFyaWFuY2UpDQotIHJhbmdlDQotIG1pbg0KLSBtYXgNCg0KTm90ZTogVGhlIGZ1bmN0aW9uIGBuYCBkb2Vzbid0IG5lZWQgYSBwYXJhbWV0ZXIsIGJlY2F1c2UgaXQganVzdCBjb3VudHMgdGhlIG51bWJlciBvZiByb3dzIGluIHRoZSBkYXRhZnJhbWUuIFRoaXMgd291bGQgYmVjb21lIG1vcmUgY2xlYXIgaW4gdGhlIGV4YW1wbGVzLg0KDQpXZSBjYW4gYWxzbyB3cml0ZSBvdXIgb3duIGZ1bmN0aW9uIGFuZCB1c2UgaGVyZS4gQnV0IHRoYXQgaXMgYSBtb3JlIGFkdmFuY2VkIHRvcGljLg0KDQpFeGFtcGxlOiBXaGF0IGlzIHRoZSBtZWFuIHJhdGluZyBmb3IgYWNjdXJhY3k/DQpgYGB7cn0NCnN1bW1hcml6ZShkZiwgbWVhbl9hY2N1cmFjeSA9IG1lYW4ocmV2aWV3X3Njb3Jlc19hY2N1cmFjeSkpDQpgYGANCg0KVGhpcyBkaWQgbm90IGdpdmUgdXMgd2hhdCB3ZSBleHBlY3RlZC4gVGhpcyBpcyBiZWNhdXNlIGFueSBhcml0aG1ldGljIG9wZXJhdGlvbiBpbnZvbHZpbmcgYE5BYCAodGhhdCwgbm90IGF2YWlsYWJsZSBvciBtaXNzaW5nIHZhbHVlcykgcmVzdWx0cyBpbiBgTkFgLiANCmBgYHtyfQ0KNSArIE5BDQpgYGANCk9yLA0KYGBge3J9DQpOQSAvIDMzDQpgYGANCg0KVGhlIHJldmlld19zY29yZXNfYWNjdXJhY3kgaGFkIHNldmVyYWwgYE5BYHMuIFRoZSBjb3JyZWN0IHdheSBvZiBkb2luZyB0aGlzIHdvdWxkIGJlOg0KYGBge3J9DQpzdW1tYXJpemUoZGYsIG1lYW5fYWNjdXJhY3kgPSBtZWFuKHJldmlld19zY29yZXNfYWNjdXJhY3ksIG5hLnJtID0gVFJVRSkpDQpgYGANCmBuYS5ybSA9IFRSVUVgIG1lYW5zICJyZW1vdmUgYWxsIE5BcyBiZWZvcmUgcGVyZm9ybWluZyBhcml0aG1ldGljIG9wZXJhdGlvbnMuIg0KDQpXaGVuIHdlIHN1bW1lZCB0aGUgYWNjb21tb2RhdGVzIGNvbHVtbiBlYXJsaWVyLCB3ZSBkaWRuJ3QgYWRkIGBuYS5ybSA9IFRSVUVgIGJlY2F1c2UgdGhhdCBjb2x1bW4gZGlkbid0IGhhdmUgYW55IE5Bcy4gDQoNCkV4YW1wbGU6IFdlIGNhbiBoYXZlIG11bHRpcGxlIHN1bW1hcmllcyBhdCB0aGUgc2FtZSB0aW1lLg0KYGBge3J9DQpzdW1tYXJpemUoZGYsIG1pbl9zY29yZV92YWx1ZSA9IG1pbihyZXZpZXdfc2NvcmVzX3ZhbHVlLCBuYS5ybSA9IFRSVUUpLCANCiAgICAgICAgICBtYXhfc2NvcmVfdmFsdWUgPSBtYXgocmV2aWV3X3Njb3Jlc192YWx1ZSwgbmEucm0gPSBUUlVFKSkNCmBgYA0KDQpFeGFtcGxlOiBXaGF0J3MgdGhlIHRvdGFsIG51bWJlciBvZiBsaXN0aW5ncyBpbiB0aGlzIGRhdGFzZXQ/DQpgYGB7cn0NCmRmICU+JSANCiAgc3VtbWFyaXplKGNvdW50ID0gbigpKQ0KYGBgDQoNCg0KKkV4ZXJjaXNlOiBUaGUgaG9zdCB3aG8ncyBiZWVuIHdpdGggQWlyYm5iIE5ZQyBmb3IgdGhlIGxvbmdlc3QgdGltZSBzdGFydGVkIG9uIHdoaWNoIGRhdGU/Kg0KYGBge3J9DQoNCmBgYA0KKkV4ZXJjaXNlOiBGaW5kIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gYW5kIG1lYW4gb2YgdGhlIHByaWNlIHZhcmlhYmxlLioNCmBgYHtyfQ0KDQpgYGANCg0KKkV4ZXJjaXNlOiBDb3VudCB0aGUgbnVtYmVyIG9mIHVuaXF1ZSB6aXBfY29kZXMgaW4gdGhlIGRhdGEuIEhpbnQgYGRpc3RpbmN0YCBhbmQgYG5gLioNCmBgYHtyfQ0KDQpgYGANCg0KKkV4ZXJjaXNlOiBIb3cgbWFueSBsaXN0aW5ncyBoYXZlIHBlcmZlY3QgMTAwIGZvciByZXZpZXdfc2NvcmVzX3JhdGluZyAod2hpY2ggaXMgdGhlIG92ZXJhbGwgcmF0aW5nIHNob3duIGFzIHN0YXJzIG9uIHRoZSBsaXN0aW5nJ3Mgd2VicGFnZSk/Kg0KYGBge3J9DQoNCmBgYA0KDQoqRXhlcmNpc2U6IEhvdyBtYW55IGxpc3RpbmdzIGhhdmUgcGVyZmVjdCAxMDAgZm9yIHJldmlld19zY29yZXNfcmF0aW5nIGFuZCBwcmljZSBsZXNzIHRoYW4gNTA/ICoNCmBgYHtyfQ0KDQpgYGANCg0KDQojIyMgZ3JvdXAtYnkNCg0KYGdyb3VwLWJ5YCBpcyBvbmUgb2YgdGhlIG1vc3QgdXNlZnVsIGZ1bmN0aW9ucy4gSXQgZGl2aWRlcyB0aGUgZGF0YSBpbnRvIGRpZmZlcmVudCBncm91cHMgYW5kIHRoZW4gYHN1bW1hcml6ZWAgc3VtbWFyaXplcyBlYWNoIG9mIHRoZSBncm91cC4gVGhpcyB3YXkgd2UgY2FuIGNvbXByZSB0aGUgZGlmZmVyZW50IGdyb3VwcyBvbiB2YXJpb3VzIGF0dHJpYnV0ZXMuDQoNCkV4YW1wbGU6IFdoYXQncyB0aGUgbWVhbiBhbmQgbWVkaWFuIHByaWNlIGluIGVhY2ggb2YgdGhlIGZpdmUgYm9yb3VnaHM/DQoNCmBgYHtyfQ0KZGYgJT4lIA0KICBncm91cF9ieShib3JvdWdoKSAlPiUgDQogIHN1bW1hcml6ZShtZWFuKHByaWNlKSwgbWVkaWFuKHByaWNlKSkNCmBgYA0KKkV4ZXJjaXNlOiBSZXR1cm4gdGhlIHNhbWUgZGF0YWZyYW1lIGJ1dCBzb3J0ZWQgYnkgbWVhbiBwcmljZS4qDQpgYGB7cn0NCg0KYGBgDQoNCkV4YW1wbGU6IFdoaWNoIGFyZSB0aGUgdG9wIDEwIG5laWdoYm91cmhvb2RzIGJ5IHRvdGFsIG51bWJlciBvZiBsaXN0aW5ncz8NCmBgYHtyfQ0KZGYgJT4lIA0KICBncm91cF9ieShuZWlnaGJvdXJob29kKSAlPiUgDQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkgJT4lIA0KICBhcnJhbmdlKGRlc2MoY291bnQpKSAlPiUgDQogIHRvcF9uKDEwKQ0KYGBgDQoNCldlIGludHJvZHVjZSBhIG5ldyBmdW5jdGlvbiBgdG9wX25gIGhlcmUsIHdoaWNoIGlzIHNlbGYtZXhwbGFuYXRvcnkuDQoNCipFeGVyY2lzZTogRG8gdGhlIHNhbWUgYXMgYWJvdmUgYnV0IG9ubHkgZm9yIGxpc3RpbmdzIHdoZXJlIHByaWNlIGlzIG1vcmUgdGhhbiA1MDAuKg0KYGBge3J9DQoNCmBgYA0KDQpFeGFtcGxlOiBJbiBNYW5oYXR0YW4sIHdoaWNoIG5laWdoYm91cmhvb2QgaGFzIHRoZSBoaWdoZXN0IG1lYW4gcHJpY2U/Kg0KYGBge3J9DQpkZiAlPiUNCiAgZmlsdGVyKGJvcm91Z2ggPT0gIk1hbmhhdHRhbiIpICU+JSANCiAgZ3JvdXBfYnkobmVpZ2hib3VyaG9vZCkgJT4lIA0KICBzdW1tYXJpemUobWVhbl9wcmljZSA9IG1lYW4ocHJpY2UpKSAlPiUgDQogIGFycmFuZ2UoZGVzYyhtZWFuX3ByaWNlKSkNCmBgYA0KDQoqRXhlcmNpc2U6IEluIEJyb254IGFuZCBRdWVlbnMsIHdoYXQncyB0aGUgbWVhbiBwcmljZSBmb3IgZGlmZmVyZW50IHByb3BlcnR5X3R5cGU/Kg0KYGBge3J9DQoNCmBgYA0KDQoqRXhlcmNpc2U6IFdoaWNoIGNvbWJpbmF0aW9uIG9mIHByb3BlcnR5X3R5cGUgYW5kIHJvb21fdHlwZSBoYXMgdGhlIGhpZ2hlc3QgbWVkaWFuIHByaWNlPw0KYGBge3J9DQoNCmBgYA0KDQoNCg0KSGVyZSBhcmUgc29tZSBleGNlbGxlbnQgcmVzb3VyY2VzIGlmIHlvdSB3YW50IHRvIGtlZXAgbGVhcm5pbmcgUjoNCg0KLSBodHRwOi8vdHJ5ci5jb2Rlc2Nob29sLmNvbQ0KLSBodHRwOi8vcjRkcy5oYWQuY28ubnovDQotIGh0dHA6Ly9zd2lybHN0YXRzLmNvbS8NCg0KDQo=